test_prettytable.py 94 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610
  1. from __future__ import annotations
  2. import datetime as dt
  3. import io
  4. import random
  5. import sqlite3
  6. from math import e, pi, sqrt
  7. from typing import Any
  8. import pytest
  9. from pytest_lazy_fixtures import lf
  10. import prettytable
  11. from 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. SINGLE_BORDER,
  24. PrettyTable,
  25. from_csv,
  26. from_db_cursor,
  27. from_html,
  28. from_html_one,
  29. from_json,
  30. )
  31. def test_version() -> None:
  32. assert isinstance(prettytable.__version__, str)
  33. assert prettytable.__version__[0].isdigit()
  34. assert prettytable.__version__.count(".") >= 2
  35. assert prettytable.__version__[-1].isdigit()
  36. def helper_table(*, rows: int = 3) -> PrettyTable:
  37. table = PrettyTable(["", "Field 1", "Field 2", "Field 3"])
  38. v = 1
  39. for row in range(rows):
  40. # Some have spaces, some not, to help test padding columns of different widths
  41. table.add_row([v, f"value {v}", f"value{v+1}", f"value{v+2}"])
  42. v += 3
  43. return table
  44. @pytest.fixture
  45. def row_prettytable() -> PrettyTable:
  46. # Row by row...
  47. table = PrettyTable()
  48. table.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  49. table.add_row(["Adelaide", 1295, 1158259, 600.5])
  50. table.add_row(["Brisbane", 5905, 1857594, 1146.4])
  51. table.add_row(["Darwin", 112, 120900, 1714.7])
  52. table.add_row(["Hobart", 1357, 205556, 619.5])
  53. table.add_row(["Sydney", 2058, 4336374, 1214.8])
  54. table.add_row(["Melbourne", 1566, 3806092, 646.9])
  55. table.add_row(["Perth", 5386, 1554769, 869.4])
  56. return table
  57. @pytest.fixture
  58. def col_prettytable() -> PrettyTable:
  59. # Column by column...
  60. table = PrettyTable()
  61. table.add_column(
  62. "City name",
  63. ["Adelaide", "Brisbane", "Darwin", "Hobart", "Sydney", "Melbourne", "Perth"],
  64. )
  65. table.add_column("Area", [1295, 5905, 112, 1357, 2058, 1566, 5386])
  66. table.add_column(
  67. "Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769]
  68. )
  69. table.add_column(
  70. "Annual Rainfall", [600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4]
  71. )
  72. return table
  73. @pytest.fixture
  74. def mix_prettytable() -> PrettyTable:
  75. # A mix of both!
  76. table = PrettyTable()
  77. table.field_names = ["City name", "Area"]
  78. table.add_row(["Adelaide", 1295])
  79. table.add_row(["Brisbane", 5905])
  80. table.add_row(["Darwin", 112])
  81. table.add_row(["Hobart", 1357])
  82. table.add_row(["Sydney", 2058])
  83. table.add_row(["Melbourne", 1566])
  84. table.add_row(["Perth", 5386])
  85. table.add_column(
  86. "Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769]
  87. )
  88. table.add_column(
  89. "Annual Rainfall", [600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4]
  90. )
  91. return table
  92. class TestNoneOption:
  93. def test_none_char_valid_option(self) -> None:
  94. PrettyTable(["Field 1", "Field 2", "Field 3"], none_format="")
  95. def test_none_char_invalid_option(self) -> None:
  96. with pytest.raises(TypeError) as exc:
  97. PrettyTable(["Field 1", "Field 2", "Field 3"], none_format=2)
  98. assert "must be a string" in str(exc.value)
  99. def test_no_value_replace_none(self) -> None:
  100. table = PrettyTable(["Field 1", "Field 2", "Field 3"])
  101. table.add_row(["value 1", None, "value 2"])
  102. assert (
  103. table.get_string().strip()
  104. == """
  105. +---------+---------+---------+
  106. | Field 1 | Field 2 | Field 3 |
  107. +---------+---------+---------+
  108. | value 1 | None | value 2 |
  109. +---------+---------+---------+
  110. """.strip()
  111. )
  112. def test_no_value_replace_none_with_default_field_names(self) -> None:
  113. table = PrettyTable()
  114. table.add_row(["value 1", "None", "value 2"])
  115. assert (
  116. table.get_string().strip()
  117. == """
  118. +---------+---------+---------+
  119. | Field 1 | Field 2 | Field 3 |
  120. +---------+---------+---------+
  121. | value 1 | None | value 2 |
  122. +---------+---------+---------+
  123. """.strip()
  124. )
  125. def test_replace_none_all(self) -> None:
  126. table = PrettyTable(["Field 1", "Field 2", "Field 3"], none_format="N/A")
  127. table.add_row(["value 1", None, "None"])
  128. assert (
  129. table.get_string().strip()
  130. == """
  131. +---------+---------+---------+
  132. | Field 1 | Field 2 | Field 3 |
  133. +---------+---------+---------+
  134. | value 1 | N/A | N/A |
  135. +---------+---------+---------+
  136. """.strip()
  137. )
  138. def test_replace_none_by_col(self) -> None:
  139. table = PrettyTable(["Field 1", "Field 2", "Field 3"])
  140. table.none_format["Field 2"] = "N/A"
  141. table.none_format["Field 3"] = ""
  142. table.add_row(["value 1", None, None])
  143. assert (
  144. table.get_string().strip()
  145. == """
  146. +---------+---------+---------+
  147. | Field 1 | Field 2 | Field 3 |
  148. +---------+---------+---------+
  149. | value 1 | N/A | |
  150. +---------+---------+---------+
  151. """.strip()
  152. )
  153. def test_replace_none_recompute_width(self) -> None:
  154. table = PrettyTable()
  155. table.add_row([None])
  156. table.none_format = "0123456789"
  157. assert (
  158. table.get_string().strip()
  159. == """
  160. +------------+
  161. | Field 1 |
  162. +------------+
  163. | 0123456789 |
  164. +------------+
  165. """.strip()
  166. )
  167. def test_replace_none_maintain_width_on_recompute(self) -> None:
  168. table = PrettyTable()
  169. table.add_row(["Hello"])
  170. table.none_format = "0123456789"
  171. assert (
  172. table.get_string().strip()
  173. == """
  174. +---------+
  175. | Field 1 |
  176. +---------+
  177. | Hello |
  178. +---------+
  179. """.strip()
  180. )
  181. def test_replace_none_recompute_width_multi_column(self) -> None:
  182. table = PrettyTable()
  183. table.add_row(["Hello", None, "World"])
  184. table.none_format = "0123456789"
  185. assert (
  186. table.get_string().strip()
  187. == """
  188. +---------+------------+---------+
  189. | Field 1 | Field 2 | Field 3 |
  190. +---------+------------+---------+
  191. | Hello | 0123456789 | World |
  192. +---------+------------+---------+
  193. """.strip()
  194. )
  195. class TestBuildEquivalence:
  196. """Make sure that building a table row-by-row and column-by-column yield the same
  197. results"""
  198. @pytest.mark.parametrize(
  199. ["left_hand", "right_hand"],
  200. [
  201. (
  202. lf("row_prettytable"),
  203. lf("col_prettytable"),
  204. ),
  205. (
  206. lf("row_prettytable"),
  207. lf("mix_prettytable"),
  208. ),
  209. ],
  210. )
  211. def test_equivalence_ascii(
  212. self, left_hand: PrettyTable, right_hand: PrettyTable
  213. ) -> None:
  214. assert left_hand.get_string() == right_hand.get_string()
  215. @pytest.mark.parametrize(
  216. ["left_hand", "right_hand"],
  217. [
  218. (
  219. lf("row_prettytable"),
  220. lf("col_prettytable"),
  221. ),
  222. (
  223. lf("row_prettytable"),
  224. lf("mix_prettytable"),
  225. ),
  226. ],
  227. )
  228. def test_equivalence_html(
  229. self, left_hand: PrettyTable, right_hand: PrettyTable
  230. ) -> None:
  231. assert left_hand.get_html_string() == right_hand.get_html_string()
  232. @pytest.mark.parametrize(
  233. ["left_hand", "right_hand"],
  234. [
  235. (
  236. lf("row_prettytable"),
  237. lf("col_prettytable"),
  238. ),
  239. (
  240. lf("row_prettytable"),
  241. lf("mix_prettytable"),
  242. ),
  243. ],
  244. )
  245. def test_equivalence_latex(
  246. self, left_hand: PrettyTable, right_hand: PrettyTable
  247. ) -> None:
  248. assert left_hand.get_latex_string() == right_hand.get_latex_string()
  249. class TestDeleteColumn:
  250. def test_delete_column(self) -> None:
  251. table = PrettyTable()
  252. table.add_column("City name", ["Adelaide", "Brisbane", "Darwin"])
  253. table.add_column("Area", [1295, 5905, 112])
  254. table.add_column("Population", [1158259, 1857594, 120900])
  255. table.del_column("Area")
  256. without_row = PrettyTable()
  257. without_row.add_column("City name", ["Adelaide", "Brisbane", "Darwin"])
  258. without_row.add_column("Population", [1158259, 1857594, 120900])
  259. assert table.get_string() == without_row.get_string()
  260. def test_delete_illegal_column_raises_error(self) -> None:
  261. table = PrettyTable()
  262. table.add_column("City name", ["Adelaide", "Brisbane", "Darwin"])
  263. with pytest.raises(ValueError):
  264. table.del_column("City not-a-name")
  265. @pytest.fixture(scope="function")
  266. def field_name_less_table() -> PrettyTable:
  267. table = PrettyTable()
  268. table.add_row(["Adelaide", 1295, 1158259, 600.5])
  269. table.add_row(["Brisbane", 5905, 1857594, 1146.4])
  270. table.add_row(["Darwin", 112, 120900, 1714.7])
  271. table.add_row(["Hobart", 1357, 205556, 619.5])
  272. table.add_row(["Sydney", 2058, 4336374, 1214.8])
  273. table.add_row(["Melbourne", 1566, 3806092, 646.9])
  274. table.add_row(["Perth", 5386, 1554769, 869.4])
  275. return table
  276. class TestFieldNameLessTable:
  277. """Make sure that building and stringing a table with no fieldnames works fine"""
  278. def test_can_string_ascii(self, field_name_less_table: prettytable) -> None:
  279. output = field_name_less_table.get_string()
  280. assert "| Field 1 | Field 2 | Field 3 | Field 4 |" in output
  281. assert "| Adelaide | 1295 | 1158259 | 600.5 |" in output
  282. def test_can_string_html(self, field_name_less_table: prettytable) -> None:
  283. output = field_name_less_table.get_html_string()
  284. assert "<th>Field 1</th>" in output
  285. assert "<td>Adelaide</td>" in output
  286. def test_can_string_latex(self, field_name_less_table: prettytable) -> None:
  287. output = field_name_less_table.get_latex_string()
  288. assert "Field 1 & Field 2 & Field 3 & Field 4 \\\\" in output
  289. assert "Adelaide & 1295 & 1158259 & 600.5 \\\\" in output
  290. def test_add_field_names_later(self, field_name_less_table: prettytable) -> None:
  291. field_name_less_table.field_names = [
  292. "City name",
  293. "Area",
  294. "Population",
  295. "Annual Rainfall",
  296. ]
  297. assert (
  298. "City name | Area | Population | Annual Rainfall"
  299. in field_name_less_table.get_string()
  300. )
  301. @pytest.fixture(scope="function")
  302. def aligned_before_table() -> PrettyTable:
  303. table = PrettyTable()
  304. table.align = "r"
  305. table.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  306. table.add_row(["Adelaide", 1295, 1158259, 600.5])
  307. table.add_row(["Brisbane", 5905, 1857594, 1146.4])
  308. table.add_row(["Darwin", 112, 120900, 1714.7])
  309. table.add_row(["Hobart", 1357, 205556, 619.5])
  310. table.add_row(["Sydney", 2058, 4336374, 1214.8])
  311. table.add_row(["Melbourne", 1566, 3806092, 646.9])
  312. table.add_row(["Perth", 5386, 1554769, 869.4])
  313. return table
  314. @pytest.fixture(scope="function")
  315. def aligned_after_table() -> PrettyTable:
  316. table = PrettyTable()
  317. table.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  318. table.add_row(["Adelaide", 1295, 1158259, 600.5])
  319. table.add_row(["Brisbane", 5905, 1857594, 1146.4])
  320. table.add_row(["Darwin", 112, 120900, 1714.7])
  321. table.add_row(["Hobart", 1357, 205556, 619.5])
  322. table.add_row(["Sydney", 2058, 4336374, 1214.8])
  323. table.add_row(["Melbourne", 1566, 3806092, 646.9])
  324. table.add_row(["Perth", 5386, 1554769, 869.4])
  325. table.align = "r"
  326. return table
  327. class TestAlignment:
  328. """Make sure alignment works regardless of when it was set"""
  329. def test_aligned_ascii(
  330. self, aligned_before_table: prettytable, aligned_after_table: prettytable
  331. ) -> None:
  332. before = aligned_before_table.get_string()
  333. after = aligned_after_table.get_string()
  334. assert before == after
  335. def test_aligned_html(
  336. self, aligned_before_table: prettytable, aligned_after_table: prettytable
  337. ) -> None:
  338. before = aligned_before_table.get_html_string()
  339. after = aligned_after_table.get_html_string()
  340. assert before == after
  341. def test_aligned_latex(
  342. self, aligned_before_table: prettytable, aligned_after_table: prettytable
  343. ) -> None:
  344. before = aligned_before_table.get_latex_string()
  345. after = aligned_after_table.get_latex_string()
  346. assert before == after
  347. @pytest.fixture(scope="function")
  348. def city_data_prettytable() -> PrettyTable:
  349. """Just build the Australian capital city data example table."""
  350. table = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
  351. table.add_row(["Adelaide", 1295, 1158259, 600.5])
  352. table.add_row(["Brisbane", 5905, 1857594, 1146.4])
  353. table.add_row(["Darwin", 112, 120900, 1714.7])
  354. table.add_row(["Hobart", 1357, 205556, 619.5])
  355. table.add_row(["Sydney", 2058, 4336374, 1214.8])
  356. table.add_row(["Melbourne", 1566, 3806092, 646.9])
  357. table.add_row(["Perth", 5386, 1554769, 869.4])
  358. return table
  359. @pytest.fixture(scope="function")
  360. def city_data_from_csv() -> PrettyTable:
  361. csv_string = """City name, Area, Population, Annual Rainfall
  362. Sydney, 2058, 4336374, 1214.8
  363. Melbourne, 1566, 3806092, 646.9
  364. Brisbane, 5905, 1857594, 1146.4
  365. Perth, 5386, 1554769, 869.4
  366. Adelaide, 1295, 1158259, 600.5
  367. Hobart, 1357, 205556, 619.5
  368. Darwin, 0112, 120900, 1714.7"""
  369. csv_fp = io.StringIO(csv_string)
  370. return from_csv(csv_fp)
  371. class TestOptionOverride:
  372. """Make sure all options are properly overwritten by get_string."""
  373. def test_border(self, city_data_prettytable: prettytable) -> None:
  374. default = city_data_prettytable.get_string()
  375. override = city_data_prettytable.get_string(border=False)
  376. assert default != override
  377. def test_header(self, city_data_prettytable) -> None:
  378. default = city_data_prettytable.get_string()
  379. override = city_data_prettytable.get_string(header=False)
  380. assert default != override
  381. def test_hrules_all(self, city_data_prettytable) -> None:
  382. default = city_data_prettytable.get_string()
  383. override = city_data_prettytable.get_string(hrules=ALL)
  384. assert default != override
  385. def test_hrules_none(self, city_data_prettytable) -> None:
  386. default = city_data_prettytable.get_string()
  387. override = city_data_prettytable.get_string(hrules=NONE)
  388. assert default != override
  389. class TestOptionAttribute:
  390. """Make sure all options which have an attribute interface work as they should.
  391. Also make sure option settings are copied correctly when a table is cloned by
  392. slicing."""
  393. def test_set_for_all_columns(self, city_data_prettytable) -> None:
  394. city_data_prettytable.field_names = sorted(city_data_prettytable.field_names)
  395. city_data_prettytable.align = "l"
  396. city_data_prettytable.max_width = 10
  397. city_data_prettytable.start = 2
  398. city_data_prettytable.end = 4
  399. city_data_prettytable.sortby = "Area"
  400. city_data_prettytable.reversesort = True
  401. city_data_prettytable.header = True
  402. city_data_prettytable.border = False
  403. city_data_prettytable.hrules = True
  404. city_data_prettytable.int_format = "4"
  405. city_data_prettytable.float_format = "2.2"
  406. city_data_prettytable.padding_width = 2
  407. city_data_prettytable.left_padding_width = 2
  408. city_data_prettytable.right_padding_width = 2
  409. city_data_prettytable.vertical_char = "!"
  410. city_data_prettytable.horizontal_char = "~"
  411. city_data_prettytable.junction_char = "*"
  412. city_data_prettytable.top_junction_char = "@"
  413. city_data_prettytable.bottom_junction_char = "#"
  414. city_data_prettytable.right_junction_char = "$"
  415. city_data_prettytable.left_junction_char = "%"
  416. city_data_prettytable.top_right_junction_char = "^"
  417. city_data_prettytable.top_left_junction_char = "&"
  418. city_data_prettytable.bottom_right_junction_char = "("
  419. city_data_prettytable.bottom_left_junction_char = ")"
  420. city_data_prettytable.format = True
  421. city_data_prettytable.attributes = {"class": "prettytable"}
  422. assert (
  423. city_data_prettytable.get_string() == city_data_prettytable[:].get_string()
  424. )
  425. def test_set_for_one_column(self, city_data_prettytable) -> None:
  426. city_data_prettytable.align["Rainfall"] = "l"
  427. city_data_prettytable.max_width["Name"] = 10
  428. city_data_prettytable.int_format["Population"] = "4"
  429. city_data_prettytable.float_format["Area"] = "2.2"
  430. assert (
  431. city_data_prettytable.get_string() == city_data_prettytable[:].get_string()
  432. )
  433. def test_preserve_internal_border(self) -> None:
  434. table = PrettyTable(preserve_internal_border=True)
  435. assert table.preserve_internal_border is True
  436. @pytest.fixture(scope="module")
  437. def db_cursor():
  438. conn = sqlite3.connect(":memory:")
  439. cur = conn.cursor()
  440. yield cur
  441. cur.close()
  442. conn.close()
  443. @pytest.fixture(scope="module")
  444. def init_db(db_cursor):
  445. db_cursor.execute(
  446. "CREATE TABLE cities "
  447. "(name TEXT, area INTEGER, population INTEGER, rainfall REAL)"
  448. )
  449. db_cursor.execute('INSERT INTO cities VALUES ("Adelaide", 1295, 1158259, 600.5)')
  450. db_cursor.execute('INSERT INTO cities VALUES ("Brisbane", 5905, 1857594, 1146.4)')
  451. db_cursor.execute('INSERT INTO cities VALUES ("Darwin", 112, 120900, 1714.7)')
  452. db_cursor.execute('INSERT INTO cities VALUES ("Hobart", 1357, 205556, 619.5)')
  453. db_cursor.execute('INSERT INTO cities VALUES ("Sydney", 2058, 4336374, 1214.8)')
  454. db_cursor.execute('INSERT INTO cities VALUES ("Melbourne", 1566, 3806092, 646.9)')
  455. db_cursor.execute('INSERT INTO cities VALUES ("Perth", 5386, 1554769, 869.4)')
  456. yield
  457. db_cursor.execute("DROP TABLE cities")
  458. class TestBasic:
  459. """Some very basic tests."""
  460. def test_table_rows(self, city_data_prettytable: PrettyTable) -> None:
  461. rows = city_data_prettytable.rows
  462. assert len(rows) == 7
  463. assert rows[0] == ["Adelaide", 1295, 1158259, 600.5]
  464. def _test_no_blank_lines(self, table: prettytable) -> None:
  465. string = table.get_string()
  466. lines = string.split("\n")
  467. assert "" not in lines
  468. def _test_all_length_equal(self, table: prettytable) -> None:
  469. string = table.get_string()
  470. lines = string.split("\n")
  471. lengths = [len(line) for line in lines]
  472. lengths = set(lengths)
  473. assert len(lengths) == 1
  474. def test_no_blank_lines(self, city_data_prettytable) -> None:
  475. """No table should ever have blank lines in it."""
  476. self._test_no_blank_lines(city_data_prettytable)
  477. def test_all_lengths_equal(self, city_data_prettytable) -> None:
  478. """All lines in a table should be of the same length."""
  479. self._test_all_length_equal(city_data_prettytable)
  480. def test_no_blank_lines_with_title(
  481. self, city_data_prettytable: PrettyTable
  482. ) -> None:
  483. """No table should ever have blank lines in it."""
  484. city_data_prettytable.title = "My table"
  485. self._test_no_blank_lines(city_data_prettytable)
  486. def test_all_lengths_equal_with_title(
  487. self, city_data_prettytable: PrettyTable
  488. ) -> None:
  489. """All lines in a table should be of the same length."""
  490. city_data_prettytable.title = "My table"
  491. self._test_all_length_equal(city_data_prettytable)
  492. def test_all_lengths_equal_with_long_title(
  493. self, city_data_prettytable: PrettyTable
  494. ) -> None:
  495. """All lines in a table should be of the same length, even with a long title."""
  496. city_data_prettytable.title = "My table (75 characters wide) " + "=" * 45
  497. self._test_all_length_equal(city_data_prettytable)
  498. def test_no_blank_lines_without_border(
  499. self, city_data_prettytable: PrettyTable
  500. ) -> None:
  501. """No table should ever have blank lines in it."""
  502. city_data_prettytable.border = False
  503. self._test_no_blank_lines(city_data_prettytable)
  504. def test_all_lengths_equal_without_border(
  505. self, city_data_prettytable: PrettyTable
  506. ) -> None:
  507. """All lines in a table should be of the same length."""
  508. city_data_prettytable.border = False
  509. self._test_all_length_equal(city_data_prettytable)
  510. def test_no_blank_lines_without_header(
  511. self, city_data_prettytable: PrettyTable
  512. ) -> None:
  513. """No table should ever have blank lines in it."""
  514. city_data_prettytable.header = False
  515. self._test_no_blank_lines(city_data_prettytable)
  516. def test_all_lengths_equal_without_header(
  517. self, city_data_prettytable: PrettyTable
  518. ) -> None:
  519. """All lines in a table should be of the same length."""
  520. city_data_prettytable.header = False
  521. self._test_all_length_equal(city_data_prettytable)
  522. def test_no_blank_lines_with_hrules_none(
  523. self, city_data_prettytable: PrettyTable
  524. ) -> None:
  525. """No table should ever have blank lines in it."""
  526. city_data_prettytable.hrules = NONE
  527. self._test_no_blank_lines(city_data_prettytable)
  528. def test_all_lengths_equal_with_hrules_none(
  529. self, city_data_prettytable: PrettyTable
  530. ) -> None:
  531. """All lines in a table should be of the same length."""
  532. city_data_prettytable.hrules = NONE
  533. self._test_all_length_equal(city_data_prettytable)
  534. def test_no_blank_lines_with_hrules_all(
  535. self, city_data_prettytable: PrettyTable
  536. ) -> None:
  537. """No table should ever have blank lines in it."""
  538. city_data_prettytable.hrules = ALL
  539. self._test_no_blank_lines(city_data_prettytable)
  540. def test_all_lengths_equal_with_hrules_all(
  541. self, city_data_prettytable: PrettyTable
  542. ) -> None:
  543. """All lines in a table should be of the same length."""
  544. city_data_prettytable.hrules = ALL
  545. self._test_all_length_equal(city_data_prettytable)
  546. def test_no_blank_lines_with_style_msword(
  547. self, city_data_prettytable: PrettyTable
  548. ) -> None:
  549. """No table should ever have blank lines in it."""
  550. city_data_prettytable.set_style(MSWORD_FRIENDLY)
  551. self._test_no_blank_lines(city_data_prettytable)
  552. def test_all_lengths_equal_with_style_msword(
  553. self, city_data_prettytable: PrettyTable
  554. ) -> None:
  555. """All lines in a table should be of the same length."""
  556. city_data_prettytable.set_style(MSWORD_FRIENDLY)
  557. self._test_all_length_equal(city_data_prettytable)
  558. def test_no_blank_lines_with_int_format(
  559. self, city_data_prettytable: PrettyTable
  560. ) -> None:
  561. """No table should ever have blank lines in it."""
  562. city_data_prettytable.int_format = "04"
  563. self._test_no_blank_lines(city_data_prettytable)
  564. def test_all_lengths_equal_with_int_format(
  565. self, city_data_prettytable: PrettyTable
  566. ) -> None:
  567. """All lines in a table should be of the same length."""
  568. city_data_prettytable.int_format = "04"
  569. self._test_all_length_equal(city_data_prettytable)
  570. def test_no_blank_lines_with_float_format(
  571. self, city_data_prettytable: PrettyTable
  572. ) -> None:
  573. """No table should ever have blank lines in it."""
  574. city_data_prettytable.float_format = "6.2f"
  575. self._test_no_blank_lines(city_data_prettytable)
  576. def test_all_lengths_equal_with_float_format(
  577. self, city_data_prettytable: PrettyTable
  578. ) -> None:
  579. """All lines in a table should be of the same length."""
  580. city_data_prettytable.float_format = "6.2f"
  581. self._test_all_length_equal(city_data_prettytable)
  582. def test_no_blank_lines_from_csv(self, city_data_from_csv: PrettyTable) -> None:
  583. """No table should ever have blank lines in it."""
  584. self._test_no_blank_lines(city_data_from_csv)
  585. def test_all_lengths_equal_from_csv(self, city_data_from_csv: PrettyTable) -> None:
  586. """All lines in a table should be of the same length."""
  587. self._test_all_length_equal(city_data_from_csv)
  588. @pytest.mark.usefixtures("init_db")
  589. def test_no_blank_lines_from_db(self, db_cursor) -> None:
  590. """No table should ever have blank lines in it."""
  591. db_cursor.execute("SELECT * FROM cities")
  592. pt = from_db_cursor(db_cursor)
  593. self._test_no_blank_lines(pt)
  594. @pytest.mark.usefixtures("init_db")
  595. def test_all_lengths_equal_from_db(self, db_cursor) -> None:
  596. """No table should ever have blank lines in it."""
  597. db_cursor.execute("SELECT * FROM cities")
  598. pt = from_db_cursor(db_cursor)
  599. self._test_all_length_equal(pt)
  600. class TestEmptyTable:
  601. """Make sure the print_empty option works"""
  602. def test_print_empty_true(self, city_data_prettytable: PrettyTable) -> None:
  603. table = PrettyTable()
  604. table.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  605. assert table.get_string(print_empty=True) != ""
  606. assert table.get_string(print_empty=True) != city_data_prettytable.get_string(
  607. print_empty=True
  608. )
  609. def test_print_empty_false(self, city_data_prettytable: PrettyTable) -> None:
  610. table = PrettyTable()
  611. table.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  612. assert table.get_string(print_empty=False) == ""
  613. assert table.get_string(print_empty=False) != city_data_prettytable.get_string(
  614. print_empty=False
  615. )
  616. def test_interaction_with_border(self) -> None:
  617. table = PrettyTable()
  618. table.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  619. assert table.get_string(border=False, print_empty=True) == ""
  620. class TestSlicing:
  621. def test_slice_all(self, city_data_prettytable: PrettyTable) -> None:
  622. table = city_data_prettytable[:]
  623. assert city_data_prettytable.get_string() == table.get_string()
  624. def test_slice_first_two_rows(self, city_data_prettytable: PrettyTable) -> None:
  625. table = city_data_prettytable[0:2]
  626. string = table.get_string()
  627. assert len(string.split("\n")) == 6
  628. assert "Adelaide" in string
  629. assert "Brisbane" in string
  630. assert "Melbourne" not in string
  631. assert "Perth" not in string
  632. def test_slice_last_two_rows(self, city_data_prettytable: PrettyTable) -> None:
  633. table = city_data_prettytable[-2:]
  634. string = table.get_string()
  635. assert len(string.split("\n")) == 6
  636. assert "Adelaide" not in string
  637. assert "Brisbane" not in string
  638. assert "Melbourne" in string
  639. assert "Perth" in string
  640. class TestSorting:
  641. def test_sort_by_different_per_columns(
  642. self, city_data_prettytable: PrettyTable
  643. ) -> None:
  644. city_data_prettytable.sortby = city_data_prettytable.field_names[0]
  645. old = city_data_prettytable.get_string()
  646. for field in city_data_prettytable.field_names[1:]:
  647. city_data_prettytable.sortby = field
  648. new = city_data_prettytable.get_string()
  649. assert new != old
  650. def test_reverse_sort(self, city_data_prettytable: PrettyTable) -> None:
  651. for field in city_data_prettytable.field_names:
  652. city_data_prettytable.sortby = field
  653. city_data_prettytable.reversesort = False
  654. forward = city_data_prettytable.get_string()
  655. city_data_prettytable.reversesort = True
  656. backward = city_data_prettytable.get_string()
  657. forward_lines = forward.split("\n")[2:] # Discard header lines
  658. backward_lines = backward.split("\n")[2:]
  659. backward_lines.reverse()
  660. assert forward_lines == backward_lines
  661. def test_sort_key(self, city_data_prettytable: PrettyTable) -> None:
  662. # Test sorting by length of city name
  663. def key(vals):
  664. vals[0] = len(vals[0])
  665. return vals
  666. city_data_prettytable.sortby = "City name"
  667. city_data_prettytable.sort_key = key
  668. assert (
  669. city_data_prettytable.get_string().strip()
  670. == """
  671. +-----------+------+------------+-----------------+
  672. | City name | Area | Population | Annual Rainfall |
  673. +-----------+------+------------+-----------------+
  674. | Perth | 5386 | 1554769 | 869.4 |
  675. | Darwin | 112 | 120900 | 1714.7 |
  676. | Hobart | 1357 | 205556 | 619.5 |
  677. | Sydney | 2058 | 4336374 | 1214.8 |
  678. | Adelaide | 1295 | 1158259 | 600.5 |
  679. | Brisbane | 5905 | 1857594 | 1146.4 |
  680. | Melbourne | 1566 | 3806092 | 646.9 |
  681. +-----------+------+------------+-----------------+
  682. """.strip()
  683. )
  684. def test_sort_slice(self) -> None:
  685. """Make sure sorting and slicing interact in the expected way"""
  686. table = PrettyTable(["Foo"])
  687. for i in range(20, 0, -1):
  688. table.add_row([i])
  689. new_style = table.get_string(sortby="Foo", end=10)
  690. assert "10" in new_style
  691. assert "20" not in new_style
  692. oldstyle = table.get_string(sortby="Foo", end=10, oldsortslice=True)
  693. assert "10" not in oldstyle
  694. assert "20" in oldstyle
  695. @pytest.fixture(scope="function")
  696. def float_pt() -> PrettyTable:
  697. table = PrettyTable(["Constant", "Value"])
  698. table.add_row(["Pi", pi])
  699. table.add_row(["e", e])
  700. table.add_row(["sqrt(2)", sqrt(2)])
  701. return table
  702. class TestFloatFormat:
  703. def test_no_decimals(self, float_pt: PrettyTable) -> None:
  704. float_pt.float_format = ".0f"
  705. float_pt.caching = False
  706. assert "." not in float_pt.get_string()
  707. def test_round_to_5dp(self, float_pt: PrettyTable) -> None:
  708. float_pt.float_format = ".5f"
  709. string = float_pt.get_string()
  710. assert "3.14159" in string
  711. assert "3.141592" not in string
  712. assert "2.71828" in string
  713. assert "2.718281" not in string
  714. assert "2.718282" not in string
  715. assert "1.41421" in string
  716. assert "1.414213" not in string
  717. def test_pad_with_2zeroes(self, float_pt: PrettyTable) -> None:
  718. float_pt.float_format = "06.2f"
  719. string = float_pt.get_string()
  720. assert "003.14" in string
  721. assert "002.72" in string
  722. assert "001.41" in string
  723. class TestBreakLine:
  724. @pytest.mark.parametrize(
  725. ["rows", "hrule", "expected_result"],
  726. [
  727. (
  728. [["value 1", "value2\nsecond line"], ["value 3", "value4"]],
  729. ALL,
  730. """
  731. +---------+-------------+
  732. | Field 1 | Field 2 |
  733. +---------+-------------+
  734. | value 1 | value2 |
  735. | | second line |
  736. +---------+-------------+
  737. | value 3 | value4 |
  738. +---------+-------------+
  739. """,
  740. ),
  741. (
  742. [
  743. ["value 1", "value2\nsecond line"],
  744. ["value 3\n\nother line", "value4\n\n\nvalue5"],
  745. ],
  746. ALL,
  747. """
  748. +------------+-------------+
  749. | Field 1 | Field 2 |
  750. +------------+-------------+
  751. | value 1 | value2 |
  752. | | second line |
  753. +------------+-------------+
  754. | value 3 | value4 |
  755. | | |
  756. | other line | |
  757. | | value5 |
  758. +------------+-------------+
  759. """,
  760. ),
  761. (
  762. [
  763. ["value 1", "value2\nsecond line"],
  764. ["value 3\n\nother line", "value4\n\n\nvalue5"],
  765. ],
  766. FRAME,
  767. """
  768. +------------+-------------+
  769. | Field 1 | Field 2 |
  770. +------------+-------------+
  771. | value 1 | value2 |
  772. | | second line |
  773. | value 3 | value4 |
  774. | | |
  775. | other line | |
  776. | | value5 |
  777. +------------+-------------+
  778. """,
  779. ),
  780. ],
  781. )
  782. def test_break_line_ascii(
  783. self, rows: list[list[Any]], hrule: int, expected_result: str
  784. ) -> None:
  785. table = PrettyTable(["Field 1", "Field 2"])
  786. for row in rows:
  787. table.add_row(row)
  788. result = table.get_string(hrules=hrule)
  789. assert result.strip() == expected_result.strip()
  790. def test_break_line_html(self) -> None:
  791. table = PrettyTable(["Field 1", "Field 2"])
  792. table.add_row(["value 1", "value2\nsecond line"])
  793. table.add_row(["value 3", "value4"])
  794. result = table.get_html_string(hrules=ALL)
  795. assert (
  796. result.strip()
  797. == """
  798. <table>
  799. <thead>
  800. <tr>
  801. <th>Field 1</th>
  802. <th>Field 2</th>
  803. </tr>
  804. </thead>
  805. <tbody>
  806. <tr>
  807. <td>value 1</td>
  808. <td>value2<br>second line</td>
  809. </tr>
  810. <tr>
  811. <td>value 3</td>
  812. <td>value4</td>
  813. </tr>
  814. </tbody>
  815. </table>
  816. """.strip()
  817. )
  818. class TestAnsiWidth:
  819. colored = "\033[31mC\033[32mO\033[31mL\033[32mO\033[31mR\033[32mE\033[31mD\033[0m"
  820. def test_color(self) -> None:
  821. table = PrettyTable(["Field 1", "Field 2"])
  822. table.add_row([self.colored, self.colored])
  823. table.add_row(["nothing", "neither"])
  824. result = table.get_string()
  825. assert (
  826. result.strip()
  827. == f"""
  828. +---------+---------+
  829. | Field 1 | Field 2 |
  830. +---------+---------+
  831. | {self.colored} | {self.colored} |
  832. | nothing | neither |
  833. +---------+---------+
  834. """.strip()
  835. )
  836. def test_reset(self) -> None:
  837. table = PrettyTable(["Field 1", "Field 2"])
  838. table.add_row(["abc def\033(B", "\033[31mabc def\033[m"])
  839. table.add_row(["nothing", "neither"])
  840. result = table.get_string()
  841. assert (
  842. result.strip()
  843. == """
  844. +---------+---------+
  845. | Field 1 | Field 2 |
  846. +---------+---------+
  847. | abc def\033(B | \033[31mabc def\033[m |
  848. | nothing | neither |
  849. +---------+---------+
  850. """.strip()
  851. )
  852. class TestFromDB:
  853. @pytest.mark.usefixtures("init_db")
  854. def test_non_select_cursor(self, db_cursor) -> None:
  855. db_cursor.execute(
  856. 'INSERT INTO cities VALUES ("Adelaide", 1295, 1158259, 600.5)'
  857. )
  858. assert from_db_cursor(db_cursor) is None
  859. class TestJSONOutput:
  860. def test_json_output(self) -> None:
  861. t = helper_table()
  862. result = t.get_json_string()
  863. assert (
  864. result.strip()
  865. == """
  866. [
  867. [
  868. "",
  869. "Field 1",
  870. "Field 2",
  871. "Field 3"
  872. ],
  873. {
  874. "": 1,
  875. "Field 1": "value 1",
  876. "Field 2": "value2",
  877. "Field 3": "value3"
  878. },
  879. {
  880. "": 4,
  881. "Field 1": "value 4",
  882. "Field 2": "value5",
  883. "Field 3": "value6"
  884. },
  885. {
  886. "": 7,
  887. "Field 1": "value 7",
  888. "Field 2": "value8",
  889. "Field 3": "value9"
  890. }
  891. ]""".strip()
  892. )
  893. options = {"fields": ["Field 1", "Field 3"]}
  894. result = t.get_json_string(**options)
  895. assert (
  896. result.strip()
  897. == """
  898. [
  899. [
  900. "Field 1",
  901. "Field 3"
  902. ],
  903. {
  904. "Field 1": "value 1",
  905. "Field 3": "value3"
  906. },
  907. {
  908. "Field 1": "value 4",
  909. "Field 3": "value6"
  910. },
  911. {
  912. "Field 1": "value 7",
  913. "Field 3": "value9"
  914. }
  915. ]""".strip()
  916. )
  917. def test_json_output_options(self) -> None:
  918. t = helper_table()
  919. result = t.get_json_string(header=False, indent=None, separators=(",", ":"))
  920. assert (
  921. result
  922. == """[{"":1,"Field 1":"value 1","Field 2":"value2","Field 3":"value3"},"""
  923. """{"":4,"Field 1":"value 4","Field 2":"value5","Field 3":"value6"},"""
  924. """{"":7,"Field 1":"value 7","Field 2":"value8","Field 3":"value9"}]"""
  925. )
  926. class TestHtmlOutput:
  927. def test_html_output(self) -> None:
  928. t = helper_table()
  929. result = t.get_html_string()
  930. assert (
  931. result.strip()
  932. == """
  933. <table>
  934. <thead>
  935. <tr>
  936. <th></th>
  937. <th>Field 1</th>
  938. <th>Field 2</th>
  939. <th>Field 3</th>
  940. </tr>
  941. </thead>
  942. <tbody>
  943. <tr>
  944. <td>1</td>
  945. <td>value 1</td>
  946. <td>value2</td>
  947. <td>value3</td>
  948. </tr>
  949. <tr>
  950. <td>4</td>
  951. <td>value 4</td>
  952. <td>value5</td>
  953. <td>value6</td>
  954. </tr>
  955. <tr>
  956. <td>7</td>
  957. <td>value 7</td>
  958. <td>value8</td>
  959. <td>value9</td>
  960. </tr>
  961. </tbody>
  962. </table>
  963. """.strip()
  964. )
  965. def test_html_output_formatted(self) -> None:
  966. t = helper_table()
  967. result = t.get_html_string(format=True)
  968. assert (
  969. result.strip()
  970. == """
  971. <table frame="box" rules="cols">
  972. <thead>
  973. <tr>
  974. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  975. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  976. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
  977. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
  978. </tr>
  979. </thead>
  980. <tbody>
  981. <tr>
  982. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
  983. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td>
  984. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td>
  985. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td>
  986. </tr>
  987. <tr>
  988. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td>
  989. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td>
  990. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td>
  991. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td>
  992. </tr>
  993. <tr>
  994. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td>
  995. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td>
  996. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td>
  997. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td>
  998. </tr>
  999. </tbody>
  1000. </table>
  1001. """.strip() # noqa: E501
  1002. )
  1003. def test_html_output_with_title(self) -> None:
  1004. t = helper_table()
  1005. t.title = "Title & Title"
  1006. result = t.get_html_string(attributes={"bgcolor": "red", "a<b": "1<2"})
  1007. assert (
  1008. result.strip()
  1009. == """
  1010. <table bgcolor="red" a&lt;b="1&lt;2">
  1011. <caption>Title &amp; Title</caption>
  1012. <thead>
  1013. <tr>
  1014. <th></th>
  1015. <th>Field 1</th>
  1016. <th>Field 2</th>
  1017. <th>Field 3</th>
  1018. </tr>
  1019. </thead>
  1020. <tbody>
  1021. <tr>
  1022. <td>1</td>
  1023. <td>value 1</td>
  1024. <td>value2</td>
  1025. <td>value3</td>
  1026. </tr>
  1027. <tr>
  1028. <td>4</td>
  1029. <td>value 4</td>
  1030. <td>value5</td>
  1031. <td>value6</td>
  1032. </tr>
  1033. <tr>
  1034. <td>7</td>
  1035. <td>value 7</td>
  1036. <td>value8</td>
  1037. <td>value9</td>
  1038. </tr>
  1039. </tbody>
  1040. </table>
  1041. """.strip()
  1042. )
  1043. def test_html_output_formatted_with_title(self) -> None:
  1044. t = helper_table()
  1045. t.title = "Title & Title"
  1046. result = t.get_html_string(
  1047. attributes={"bgcolor": "red", "a<b": "1<2"}, format=True
  1048. )
  1049. assert (
  1050. result.strip()
  1051. == """
  1052. <table frame="box" rules="cols" bgcolor="red" a&lt;b="1&lt;2">
  1053. <caption>Title &amp; Title</caption>
  1054. <thead>
  1055. <tr>
  1056. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  1057. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  1058. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
  1059. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
  1060. </tr>
  1061. </thead>
  1062. <tbody>
  1063. <tr>
  1064. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
  1065. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td>
  1066. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td>
  1067. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td>
  1068. </tr>
  1069. <tr>
  1070. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td>
  1071. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td>
  1072. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td>
  1073. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td>
  1074. </tr>
  1075. <tr>
  1076. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td>
  1077. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td>
  1078. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td>
  1079. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td>
  1080. </tr>
  1081. </tbody>
  1082. </table>
  1083. """.strip() # noqa: E501
  1084. )
  1085. def test_html_output_without_escaped_header(self) -> None:
  1086. t = helper_table(rows=0)
  1087. t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
  1088. result = t.get_html_string(escape_header=False)
  1089. assert (
  1090. result.strip()
  1091. == """
  1092. <table>
  1093. <thead>
  1094. <tr>
  1095. <th></th>
  1096. <th>Field 1</th>
  1097. <th><em>Field 2</em></th>
  1098. <th><a href='#'>Field 3</a></th>
  1099. </tr>
  1100. </thead>
  1101. <tbody>
  1102. </tbody>
  1103. </table>
  1104. """.strip()
  1105. )
  1106. def test_html_output_without_escaped_data(self) -> None:
  1107. t = helper_table(rows=0)
  1108. t.add_row(
  1109. [
  1110. 1,
  1111. "<b>value 1</b>",
  1112. "<span style='text-decoration: underline;'>value2</span>",
  1113. "<a href='#'>value3</a>",
  1114. ]
  1115. )
  1116. result = t.get_html_string(escape_data=False)
  1117. assert (
  1118. result.strip()
  1119. == """
  1120. <table>
  1121. <thead>
  1122. <tr>
  1123. <th></th>
  1124. <th>Field 1</th>
  1125. <th>Field 2</th>
  1126. <th>Field 3</th>
  1127. </tr>
  1128. </thead>
  1129. <tbody>
  1130. <tr>
  1131. <td>1</td>
  1132. <td><b>value 1</b></td>
  1133. <td><span style='text-decoration: underline;'>value2</span></td>
  1134. <td><a href='#'>value3</a></td>
  1135. </tr>
  1136. </tbody>
  1137. </table>
  1138. """.strip()
  1139. )
  1140. def test_html_output_with_escaped_header(self) -> None:
  1141. t = helper_table(rows=0)
  1142. t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
  1143. result = t.get_html_string(escape_header=True)
  1144. assert (
  1145. result.strip()
  1146. == """
  1147. <table>
  1148. <thead>
  1149. <tr>
  1150. <th></th>
  1151. <th>Field 1</th>
  1152. <th>&lt;em&gt;Field 2&lt;/em&gt;</th>
  1153. <th>&lt;a href=&#x27;#&#x27;&gt;Field 3&lt;/a&gt;</th>
  1154. </tr>
  1155. </thead>
  1156. <tbody>
  1157. </tbody>
  1158. </table>
  1159. """.strip()
  1160. )
  1161. def test_html_output_with_escaped_data(self) -> None:
  1162. t = helper_table(rows=0)
  1163. t.add_row(
  1164. [
  1165. 1,
  1166. "<b>value 1</b>",
  1167. "<span style='text-decoration: underline;'>value2</span>",
  1168. "<a href='#'>value3</a>",
  1169. ]
  1170. )
  1171. result = t.get_html_string(escape_data=True)
  1172. assert (
  1173. result.strip()
  1174. == """
  1175. <table>
  1176. <thead>
  1177. <tr>
  1178. <th></th>
  1179. <th>Field 1</th>
  1180. <th>Field 2</th>
  1181. <th>Field 3</th>
  1182. </tr>
  1183. </thead>
  1184. <tbody>
  1185. <tr>
  1186. <td>1</td>
  1187. <td>&lt;b&gt;value 1&lt;/b&gt;</td>
  1188. <td>&lt;span style=&#x27;text-decoration: underline;&#x27;&gt;value2&lt;/span&gt;</td>
  1189. <td>&lt;a href=&#x27;#&#x27;&gt;value3&lt;/a&gt;</td>
  1190. </tr>
  1191. </tbody>
  1192. </table>
  1193. """.strip() # noqa: E501
  1194. )
  1195. def test_html_output_formatted_without_escaped_header(self) -> None:
  1196. t = helper_table(rows=0)
  1197. t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
  1198. result = t.get_html_string(escape_header=False, format=True)
  1199. assert (
  1200. result.strip()
  1201. == """
  1202. <table frame="box" rules="cols">
  1203. <thead>
  1204. <tr>
  1205. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  1206. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  1207. <th style="padding-left: 1em; padding-right: 1em; text-align: center"><em>Field 2</em></th>
  1208. <th style="padding-left: 1em; padding-right: 1em; text-align: center"><a href='#'>Field 3</a></th>
  1209. </tr>
  1210. </thead>
  1211. <tbody>
  1212. </tbody>
  1213. </table>
  1214. """.strip() # noqa: E501
  1215. )
  1216. def test_html_output_formatted_without_escaped_data(self) -> None:
  1217. t = helper_table(rows=0)
  1218. t.add_row(
  1219. [
  1220. 1,
  1221. "<b>value 1</b>",
  1222. "<span style='text-decoration: underline;'>value2</span>",
  1223. "<a href='#'>value3</a>",
  1224. ]
  1225. )
  1226. result = t.get_html_string(escape_data=False, format=True)
  1227. assert (
  1228. result.strip()
  1229. == """
  1230. <table frame="box" rules="cols">
  1231. <thead>
  1232. <tr>
  1233. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  1234. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  1235. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
  1236. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
  1237. </tr>
  1238. </thead>
  1239. <tbody>
  1240. <tr>
  1241. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
  1242. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><b>value 1</b></td>
  1243. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><span style='text-decoration: underline;'>value2</span></td>
  1244. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><a href='#'>value3</a></td>
  1245. </tr>
  1246. </tbody>
  1247. </table>
  1248. """.strip() # noqa: E501
  1249. )
  1250. def test_html_output_formatted_with_escaped_header(self) -> None:
  1251. t = helper_table(rows=0)
  1252. t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"]
  1253. result = t.get_html_string(escape_header=True, format=True)
  1254. assert (
  1255. result.strip()
  1256. == """
  1257. <table frame="box" rules="cols">
  1258. <thead>
  1259. <tr>
  1260. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  1261. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  1262. <th style="padding-left: 1em; padding-right: 1em; text-align: center">&lt;em&gt;Field 2&lt;/em&gt;</th>
  1263. <th style="padding-left: 1em; padding-right: 1em; text-align: center">&lt;a href=&#x27;#&#x27;&gt;Field 3&lt;/a&gt;</th>
  1264. </tr>
  1265. </thead>
  1266. <tbody>
  1267. </tbody>
  1268. </table>
  1269. """.strip() # noqa: E501
  1270. )
  1271. def test_html_output_formatted_with_escaped_data(self) -> None:
  1272. t = helper_table(rows=0)
  1273. t.add_row(
  1274. [
  1275. 1,
  1276. "<b>value 1</b>",
  1277. "<span style='text-decoration: underline;'>value2</span>",
  1278. "<a href='#'>value3</a>",
  1279. ]
  1280. )
  1281. result = t.get_html_string(escape_data=True, format=True)
  1282. assert (
  1283. result.strip()
  1284. == """
  1285. <table frame="box" rules="cols">
  1286. <thead>
  1287. <tr>
  1288. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  1289. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  1290. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
  1291. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
  1292. </tr>
  1293. </thead>
  1294. <tbody>
  1295. <tr>
  1296. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
  1297. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">&lt;b&gt;value 1&lt;/b&gt;</td>
  1298. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">&lt;span style=&#x27;text-decoration: underline;&#x27;&gt;value2&lt;/span&gt;</td>
  1299. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">&lt;a href=&#x27;#&#x27;&gt;value3&lt;/a&gt;</td>
  1300. </tr>
  1301. </tbody>
  1302. </table>
  1303. """.strip() # noqa: E501
  1304. )
  1305. class TestPositionalJunctions:
  1306. """Verify different cases for positional-junction characters"""
  1307. def test_default(self, city_data_prettytable: PrettyTable) -> None:
  1308. city_data_prettytable.set_style(DOUBLE_BORDER)
  1309. assert (
  1310. city_data_prettytable.get_string().strip()
  1311. == """
  1312. ╔═══════════╦══════╦════════════╦═════════════════╗
  1313. ║ City name ║ Area ║ Population ║ Annual Rainfall ║
  1314. ╠═══════════╬══════╬════════════╬═════════════════╣
  1315. ║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║
  1316. ║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║
  1317. ║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║
  1318. ║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║
  1319. ║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║
  1320. ║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║
  1321. ║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║
  1322. ╚═══════════╩══════╩════════════╩═════════════════╝""".strip()
  1323. )
  1324. def test_no_header(self, city_data_prettytable: PrettyTable) -> None:
  1325. city_data_prettytable.set_style(DOUBLE_BORDER)
  1326. city_data_prettytable.header = False
  1327. assert (
  1328. city_data_prettytable.get_string().strip()
  1329. == """
  1330. ╔═══════════╦══════╦═════════╦════════╗
  1331. ║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║
  1332. ║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║
  1333. ║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║
  1334. ║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║
  1335. ║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║
  1336. ║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║
  1337. ║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║
  1338. ╚═══════════╩══════╩═════════╩════════╝""".strip()
  1339. )
  1340. def test_with_title(self, city_data_prettytable: PrettyTable) -> None:
  1341. city_data_prettytable.set_style(DOUBLE_BORDER)
  1342. city_data_prettytable.title = "Title"
  1343. assert (
  1344. city_data_prettytable.get_string().strip()
  1345. == """
  1346. ╔═════════════════════════════════════════════════╗
  1347. ║ Title ║
  1348. ╠═══════════╦══════╦════════════╦═════════════════╣
  1349. ║ City name ║ Area ║ Population ║ Annual Rainfall ║
  1350. ╠═══════════╬══════╬════════════╬═════════════════╣
  1351. ║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║
  1352. ║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║
  1353. ║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║
  1354. ║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║
  1355. ║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║
  1356. ║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║
  1357. ║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║
  1358. ╚═══════════╩══════╩════════════╩═════════════════╝""".strip()
  1359. )
  1360. def test_with_title_no_header(self, city_data_prettytable: PrettyTable) -> None:
  1361. city_data_prettytable.set_style(DOUBLE_BORDER)
  1362. city_data_prettytable.title = "Title"
  1363. city_data_prettytable.header = False
  1364. assert (
  1365. city_data_prettytable.get_string().strip()
  1366. == """
  1367. ╔═════════════════════════════════════╗
  1368. ║ Title ║
  1369. ╠═══════════╦══════╦═════════╦════════╣
  1370. ║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║
  1371. ║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║
  1372. ║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║
  1373. ║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║
  1374. ║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║
  1375. ║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║
  1376. ║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║
  1377. ╚═══════════╩══════╩═════════╩════════╝""".strip()
  1378. )
  1379. def test_hrule_all(self, city_data_prettytable: PrettyTable) -> None:
  1380. city_data_prettytable.set_style(DOUBLE_BORDER)
  1381. city_data_prettytable.title = "Title"
  1382. city_data_prettytable.hrules = ALL
  1383. assert (
  1384. city_data_prettytable.get_string().strip()
  1385. == """
  1386. ╔═════════════════════════════════════════════════╗
  1387. ║ Title ║
  1388. ╠═══════════╦══════╦════════════╦═════════════════╣
  1389. ║ City name ║ Area ║ Population ║ Annual Rainfall ║
  1390. ╠═══════════╬══════╬════════════╬═════════════════╣
  1391. ║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║
  1392. ╠═══════════╬══════╬════════════╬═════════════════╣
  1393. ║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║
  1394. ╠═══════════╬══════╬════════════╬═════════════════╣
  1395. ║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║
  1396. ╠═══════════╬══════╬════════════╬═════════════════╣
  1397. ║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║
  1398. ╠═══════════╬══════╬════════════╬═════════════════╣
  1399. ║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║
  1400. ╠═══════════╬══════╬════════════╬═════════════════╣
  1401. ║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║
  1402. ╠═══════════╬══════╬════════════╬═════════════════╣
  1403. ║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║
  1404. ╚═══════════╩══════╩════════════╩═════════════════╝""".strip()
  1405. )
  1406. def test_vrules_none(self, city_data_prettytable: PrettyTable) -> None:
  1407. city_data_prettytable.set_style(DOUBLE_BORDER)
  1408. city_data_prettytable.vrules = NONE
  1409. assert (
  1410. city_data_prettytable.get_string().strip()
  1411. == "═══════════════════════════════════════════════════\n"
  1412. " City name Area Population Annual Rainfall \n"
  1413. "═══════════════════════════════════════════════════\n"
  1414. " Adelaide 1295 1158259 600.5 \n"
  1415. " Brisbane 5905 1857594 1146.4 \n"
  1416. " Darwin 112 120900 1714.7 \n"
  1417. " Hobart 1357 205556 619.5 \n"
  1418. " Sydney 2058 4336374 1214.8 \n"
  1419. " Melbourne 1566 3806092 646.9 \n"
  1420. " Perth 5386 1554769 869.4 \n"
  1421. "═══════════════════════════════════════════════════".strip()
  1422. )
  1423. def test_vrules_frame_with_title(self, city_data_prettytable: PrettyTable) -> None:
  1424. city_data_prettytable.set_style(DOUBLE_BORDER)
  1425. city_data_prettytable.vrules = FRAME
  1426. city_data_prettytable.title = "Title"
  1427. assert (
  1428. city_data_prettytable.get_string().strip()
  1429. == """
  1430. ╔═════════════════════════════════════════════════╗
  1431. ║ Title ║
  1432. ╠═════════════════════════════════════════════════╣
  1433. ║ City name Area Population Annual Rainfall ║
  1434. ╠═════════════════════════════════════════════════╣
  1435. ║ Adelaide 1295 1158259 600.5 ║
  1436. ║ Brisbane 5905 1857594 1146.4 ║
  1437. ║ Darwin 112 120900 1714.7 ║
  1438. ║ Hobart 1357 205556 619.5 ║
  1439. ║ Sydney 2058 4336374 1214.8 ║
  1440. ║ Melbourne 1566 3806092 646.9 ║
  1441. ║ Perth 5386 1554769 869.4 ║
  1442. ╚═════════════════════════════════════════════════╝""".strip()
  1443. )
  1444. class TestStyle:
  1445. @pytest.mark.parametrize(
  1446. "style, expected",
  1447. [
  1448. pytest.param(
  1449. DEFAULT,
  1450. """
  1451. +---+---------+---------+---------+
  1452. | | Field 1 | Field 2 | Field 3 |
  1453. +---+---------+---------+---------+
  1454. | 1 | value 1 | value2 | value3 |
  1455. | 4 | value 4 | value5 | value6 |
  1456. | 7 | value 7 | value8 | value9 |
  1457. +---+---------+---------+---------+
  1458. """,
  1459. id="DEFAULT",
  1460. ),
  1461. pytest.param(
  1462. MARKDOWN, # TODO fix
  1463. """
  1464. | | Field 1 | Field 2 | Field 3 |
  1465. | :-: | :-----: | :-----: | :-----: |
  1466. | 1 | value 1 | value2 | value3 |
  1467. | 4 | value 4 | value5 | value6 |
  1468. | 7 | value 7 | value8 | value9 |
  1469. """,
  1470. id="MARKDOWN",
  1471. ),
  1472. pytest.param(
  1473. MSWORD_FRIENDLY,
  1474. """
  1475. | | Field 1 | Field 2 | Field 3 |
  1476. | 1 | value 1 | value2 | value3 |
  1477. | 4 | value 4 | value5 | value6 |
  1478. | 7 | value 7 | value8 | value9 |
  1479. """,
  1480. id="MSWORD_FRIENDLY",
  1481. ),
  1482. pytest.param(
  1483. ORGMODE,
  1484. """
  1485. |---+---------+---------+---------|
  1486. | | Field 1 | Field 2 | Field 3 |
  1487. |---+---------+---------+---------|
  1488. | 1 | value 1 | value2 | value3 |
  1489. | 4 | value 4 | value5 | value6 |
  1490. | 7 | value 7 | value8 | value9 |
  1491. |---+---------+---------+---------|
  1492. """,
  1493. id="ORGMODE",
  1494. ),
  1495. pytest.param(
  1496. PLAIN_COLUMNS,
  1497. """
  1498. Field 1 Field 2 Field 3
  1499. 1 value 1 value2 value3
  1500. 4 value 4 value5 value6
  1501. 7 value 7 value8 value9
  1502. """, # noqa: W291
  1503. id="PLAIN_COLUMNS",
  1504. ),
  1505. pytest.param(
  1506. RANDOM,
  1507. """
  1508. '^^^^^'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^'
  1509. % 1% value 1% value2% value3%
  1510. '^^^^^'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^'
  1511. % 4% value 4% value5% value6%
  1512. '^^^^^'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^'
  1513. % 7% value 7% value8% value9%
  1514. '^^^^^'^^^^^^^^^^^'^^^^^^^^^^'^^^^^^^^^^'
  1515. """,
  1516. id="RANDOM",
  1517. ),
  1518. pytest.param(
  1519. DOUBLE_BORDER,
  1520. """
  1521. ╔═══╦═════════╦═════════╦═════════╗
  1522. ║ ║ Field 1 ║ Field 2 ║ Field 3 ║
  1523. ╠═══╬═════════╬═════════╬═════════╣
  1524. ║ 1 ║ value 1 ║ value2 ║ value3 ║
  1525. ║ 4 ║ value 4 ║ value5 ║ value6 ║
  1526. ║ 7 ║ value 7 ║ value8 ║ value9 ║
  1527. ╚═══╩═════════╩═════════╩═════════╝
  1528. """,
  1529. ),
  1530. pytest.param(
  1531. SINGLE_BORDER,
  1532. """
  1533. ┌───┬─────────┬─────────┬─────────┐
  1534. │ │ Field 1 │ Field 2 │ Field 3 │
  1535. ├───┼─────────┼─────────┼─────────┤
  1536. │ 1 │ value 1 │ value2 │ value3 │
  1537. │ 4 │ value 4 │ value5 │ value6 │
  1538. │ 7 │ value 7 │ value8 │ value9 │
  1539. └───┴─────────┴─────────┴─────────┘
  1540. """,
  1541. ),
  1542. ],
  1543. )
  1544. def test_style(self, style, expected) -> None:
  1545. # Arrange
  1546. t = helper_table()
  1547. random.seed(1234)
  1548. # Act
  1549. t.set_style(style)
  1550. # Assert
  1551. result = t.get_string()
  1552. assert result.strip() == expected.strip()
  1553. def test_style_invalid(self) -> None:
  1554. # Arrange
  1555. t = helper_table()
  1556. # Act / Assert
  1557. # This is an hrule style, not a table style
  1558. with pytest.raises(ValueError):
  1559. t.set_style(ALL)
  1560. @pytest.mark.parametrize(
  1561. "style, expected",
  1562. [
  1563. pytest.param(
  1564. MARKDOWN,
  1565. """
  1566. | l | c | r | Align left | Align centre | Align right |
  1567. | :-| :-: |-: | :----------| :----------: |-----------: |
  1568. | 1 | 2 | 3 | value 1 | value2 | value3 |
  1569. | 4 | 5 | 6 | value 4 | value5 | value6 |
  1570. | 7 | 8 | 9 | value 7 | value8 | value9 |
  1571. """,
  1572. id="MARKDOWN",
  1573. ),
  1574. ],
  1575. )
  1576. def test_style_align(self, style, expected) -> None:
  1577. # Arrange
  1578. t = PrettyTable(["l", "c", "r", "Align left", "Align centre", "Align right"])
  1579. v = 1
  1580. for row in range(3):
  1581. # Some have spaces, some not, to help test padding columns of
  1582. # different widths
  1583. t.add_row([v, v + 1, v + 2, f"value {v}", f"value{v + 1}", f"value{v + 2}"])
  1584. v += 3
  1585. # Act
  1586. t.set_style(style)
  1587. t.align["l"] = t.align["Align left"] = "l"
  1588. t.align["c"] = t.align["Align centre"] = "c"
  1589. t.align["r"] = t.align["Align right"] = "r"
  1590. # Assert
  1591. result = t.get_string()
  1592. assert result.strip() == expected.strip()
  1593. class TestCsvOutput:
  1594. def test_csv_output(self) -> None:
  1595. t = helper_table()
  1596. assert t.get_csv_string(delimiter="\t", header=False) == (
  1597. "1\tvalue 1\tvalue2\tvalue3\r\n"
  1598. "4\tvalue 4\tvalue5\tvalue6\r\n"
  1599. "7\tvalue 7\tvalue8\tvalue9\r\n"
  1600. )
  1601. assert t.get_csv_string() == (
  1602. ",Field 1,Field 2,Field 3\r\n"
  1603. "1,value 1,value2,value3\r\n"
  1604. "4,value 4,value5,value6\r\n"
  1605. "7,value 7,value8,value9\r\n"
  1606. )
  1607. options = {"fields": ["Field 1", "Field 3"]}
  1608. assert t.get_csv_string(**options) == (
  1609. "Field 1,Field 3\r\n"
  1610. "value 1,value3\r\n"
  1611. "value 4,value6\r\n"
  1612. "value 7,value9\r\n"
  1613. )
  1614. class TestLatexOutput:
  1615. def test_latex_output(self) -> None:
  1616. t = helper_table()
  1617. assert t.get_latex_string() == (
  1618. "\\begin{tabular}{cccc}\r\n"
  1619. " & Field 1 & Field 2 & Field 3 \\\\\r\n"
  1620. "1 & value 1 & value2 & value3 \\\\\r\n"
  1621. "4 & value 4 & value5 & value6 \\\\\r\n"
  1622. "7 & value 7 & value8 & value9 \\\\\r\n"
  1623. "\\end{tabular}"
  1624. )
  1625. options = {"fields": ["Field 1", "Field 3"]}
  1626. assert t.get_latex_string(**options) == (
  1627. "\\begin{tabular}{cc}\r\n"
  1628. "Field 1 & Field 3 \\\\\r\n"
  1629. "value 1 & value3 \\\\\r\n"
  1630. "value 4 & value6 \\\\\r\n"
  1631. "value 7 & value9 \\\\\r\n"
  1632. "\\end{tabular}"
  1633. )
  1634. def test_latex_output_formatted(self) -> None:
  1635. t = helper_table()
  1636. assert t.get_latex_string(format=True) == (
  1637. "\\begin{tabular}{|c|c|c|c|}\r\n"
  1638. "\\hline\r\n"
  1639. " & Field 1 & Field 2 & Field 3 \\\\\r\n"
  1640. "1 & value 1 & value2 & value3 \\\\\r\n"
  1641. "4 & value 4 & value5 & value6 \\\\\r\n"
  1642. "7 & value 7 & value8 & value9 \\\\\r\n"
  1643. "\\hline\r\n"
  1644. "\\end{tabular}"
  1645. )
  1646. options = {"fields": ["Field 1", "Field 3"]}
  1647. assert t.get_latex_string(format=True, **options) == (
  1648. "\\begin{tabular}{|c|c|}\r\n"
  1649. "\\hline\r\n"
  1650. "Field 1 & Field 3 \\\\\r\n"
  1651. "value 1 & value3 \\\\\r\n"
  1652. "value 4 & value6 \\\\\r\n"
  1653. "value 7 & value9 \\\\\r\n"
  1654. "\\hline\r\n"
  1655. "\\end{tabular}"
  1656. )
  1657. options = {"vrules": FRAME}
  1658. assert t.get_latex_string(format=True, **options) == (
  1659. "\\begin{tabular}{|cccc|}\r\n"
  1660. "\\hline\r\n"
  1661. " & Field 1 & Field 2 & Field 3 \\\\\r\n"
  1662. "1 & value 1 & value2 & value3 \\\\\r\n"
  1663. "4 & value 4 & value5 & value6 \\\\\r\n"
  1664. "7 & value 7 & value8 & value9 \\\\\r\n"
  1665. "\\hline\r\n"
  1666. "\\end{tabular}"
  1667. )
  1668. options = {"hrules": ALL}
  1669. assert t.get_latex_string(format=True, **options) == (
  1670. "\\begin{tabular}{|c|c|c|c|}\r\n"
  1671. "\\hline\r\n"
  1672. " & Field 1 & Field 2 & Field 3 \\\\\r\n"
  1673. "\\hline\r\n"
  1674. "1 & value 1 & value2 & value3 \\\\\r\n"
  1675. "\\hline\r\n"
  1676. "4 & value 4 & value5 & value6 \\\\\r\n"
  1677. "\\hline\r\n"
  1678. "7 & value 7 & value8 & value9 \\\\\r\n"
  1679. "\\hline\r\n"
  1680. "\\end{tabular}"
  1681. )
  1682. def test_latex_output_header(self) -> None:
  1683. t = helper_table()
  1684. assert t.get_latex_string(format=True, hrules=HEADER) == (
  1685. "\\begin{tabular}{|c|c|c|c|}\r\n"
  1686. " & Field 1 & Field 2 & Field 3 \\\\\r\n"
  1687. "\\hline\r\n"
  1688. "1 & value 1 & value2 & value3 \\\\\r\n"
  1689. "4 & value 4 & value5 & value6 \\\\\r\n"
  1690. "7 & value 7 & value8 & value9 \\\\\r\n"
  1691. "\\end{tabular}"
  1692. )
  1693. class TestJSONConstructor:
  1694. def test_json_and_back(self, city_data_prettytable: PrettyTable) -> None:
  1695. json_string = city_data_prettytable.get_json_string()
  1696. new_table = from_json(json_string)
  1697. assert new_table.get_string() == city_data_prettytable.get_string()
  1698. class TestHtmlConstructor:
  1699. def test_html_and_back(self, city_data_prettytable: PrettyTable) -> None:
  1700. html_string = city_data_prettytable.get_html_string()
  1701. new_table = from_html(html_string)[0]
  1702. assert new_table.get_string() == city_data_prettytable.get_string()
  1703. def test_html_one_and_back(self, city_data_prettytable: PrettyTable) -> None:
  1704. html_string = city_data_prettytable.get_html_string()
  1705. new_table = from_html_one(html_string)
  1706. assert new_table.get_string() == city_data_prettytable.get_string()
  1707. def test_html_one_fail_on_many(self, city_data_prettytable: PrettyTable) -> None:
  1708. html_string = city_data_prettytable.get_html_string()
  1709. html_string += city_data_prettytable.get_html_string()
  1710. with pytest.raises(ValueError):
  1711. from_html_one(html_string)
  1712. @pytest.fixture
  1713. def japanese_pretty_table() -> PrettyTable:
  1714. table = PrettyTable(["Kanji", "Hiragana", "English"])
  1715. table.add_row(["神戸", "こうべ", "Kobe"])
  1716. table.add_row(["京都", "きょうと", "Kyoto"])
  1717. table.add_row(["長崎", "ながさき", "Nagasaki"])
  1718. table.add_row(["名古屋", "なごや", "Nagoya"])
  1719. table.add_row(["大阪", "おおさか", "Osaka"])
  1720. table.add_row(["札幌", "さっぽろ", "Sapporo"])
  1721. table.add_row(["東京", "とうきょう", "Tokyo"])
  1722. table.add_row(["横浜", "よこはま", "Yokohama"])
  1723. return table
  1724. @pytest.fixture
  1725. def emoji_pretty_table() -> PrettyTable:
  1726. thunder1 = [
  1727. '\033[38;5;226m _`/""\033[38;5;250m.-. \033[0m',
  1728. "\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m",
  1729. "\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
  1730. "\033[38;5;228;5m ⚡\033[38;5;111;25mʻ ʻ\033[38;5;228;5m"
  1731. "⚡\033[38;5;111;25mʻ ʻ \033[0m",
  1732. "\033[38;5;111m ʻ ʻ ʻ ʻ \033[0m",
  1733. ]
  1734. thunder2 = [
  1735. "\033[38;5;240;1m .-. \033[0m",
  1736. "\033[38;5;240;1m ( ). \033[0m",
  1737. "\033[38;5;240;1m (___(__) \033[0m",
  1738. "\033[38;5;21;1m ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚\033[38;5;228;5m"
  1739. "⚡\033[38;5;21;25m‚ʻ \033[0m",
  1740. "\033[38;5;21;1m ‚ʻ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚ʻ \033[0m",
  1741. ]
  1742. table = PrettyTable(["Thunderbolt", "Lightning"])
  1743. for i in range(len(thunder1)):
  1744. table.add_row([thunder1[i], thunder2[i]])
  1745. return table
  1746. class TestMultiPattern:
  1747. @pytest.mark.parametrize(
  1748. ["pt", "expected_output", "test_type"],
  1749. [
  1750. (
  1751. lf("city_data_prettytable"),
  1752. """
  1753. +-----------+------+------------+-----------------+
  1754. | City name | Area | Population | Annual Rainfall |
  1755. +-----------+------+------------+-----------------+
  1756. | Adelaide | 1295 | 1158259 | 600.5 |
  1757. | Brisbane | 5905 | 1857594 | 1146.4 |
  1758. | Darwin | 112 | 120900 | 1714.7 |
  1759. | Hobart | 1357 | 205556 | 619.5 |
  1760. | Sydney | 2058 | 4336374 | 1214.8 |
  1761. | Melbourne | 1566 | 3806092 | 646.9 |
  1762. | Perth | 5386 | 1554769 | 869.4 |
  1763. +-----------+------+------------+-----------------+
  1764. """,
  1765. "English Table",
  1766. ),
  1767. (
  1768. lf("japanese_pretty_table"),
  1769. """
  1770. +--------+------------+----------+
  1771. | Kanji | Hiragana | English |
  1772. +--------+------------+----------+
  1773. | 神戸 | こうべ | Kobe |
  1774. | 京都 | きょうと | Kyoto |
  1775. | 長崎 | ながさき | Nagasaki |
  1776. | 名古屋 | なごや | Nagoya |
  1777. | 大阪 | おおさか | Osaka |
  1778. | 札幌 | さっぽろ | Sapporo |
  1779. | 東京 | とうきょう | Tokyo |
  1780. | 横浜 | よこはま | Yokohama |
  1781. +--------+------------+----------+
  1782. """,
  1783. "Japanese table",
  1784. ),
  1785. (
  1786. lf("emoji_pretty_table"),
  1787. """
  1788. +-----------------+-----------------+
  1789. | Thunderbolt | Lightning |
  1790. +-----------------+-----------------+
  1791. | \x1b[38;5;226m _`/""\x1b[38;5;250m.-. \x1b[0m | \x1b[38;5;240;1m .-. \x1b[0m |
  1792. | \x1b[38;5;226m ,\\_\x1b[38;5;250m( ). \x1b[0m | \x1b[38;5;240;1m ( ). \x1b[0m |
  1793. | \x1b[38;5;226m /\x1b[38;5;250m(___(__) \x1b[0m | \x1b[38;5;240;1m (___(__) \x1b[0m |
  1794. | \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 |
  1795. | \x1b[38;5;111m ʻ ʻ ʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚ʻ \x1b[0m |
  1796. +-----------------+-----------------+
  1797. """, # noqa: E501
  1798. "Emoji table",
  1799. ),
  1800. ],
  1801. )
  1802. def test_multi_pattern_outputs(
  1803. self, pt: PrettyTable, expected_output: str, test_type: str
  1804. ) -> None:
  1805. printed_table = pt.get_string()
  1806. assert (
  1807. printed_table.strip() == expected_output.strip()
  1808. ), f"Error output for test output of type {test_type}"
  1809. def test_paginate() -> None:
  1810. # Arrange
  1811. t = helper_table(rows=7)
  1812. expected_page_1 = """
  1813. +----+----------+---------+---------+
  1814. | | Field 1 | Field 2 | Field 3 |
  1815. +----+----------+---------+---------+
  1816. | 1 | value 1 | value2 | value3 |
  1817. | 4 | value 4 | value5 | value6 |
  1818. | 7 | value 7 | value8 | value9 |
  1819. | 10 | value 10 | value11 | value12 |
  1820. +----+----------+---------+---------+
  1821. """.strip()
  1822. expected_page_2 = """
  1823. +----+----------+---------+---------+
  1824. | | Field 1 | Field 2 | Field 3 |
  1825. +----+----------+---------+---------+
  1826. | 13 | value 13 | value14 | value15 |
  1827. | 16 | value 16 | value17 | value18 |
  1828. | 19 | value 19 | value20 | value21 |
  1829. +----+----------+---------+---------+
  1830. """.strip()
  1831. # Act
  1832. paginated = t.paginate(page_length=4)
  1833. # Assert
  1834. paginated = paginated.strip()
  1835. assert paginated.startswith(expected_page_1)
  1836. assert "\f" in paginated
  1837. assert paginated.endswith(expected_page_2)
  1838. # Act
  1839. paginated = t.paginate(page_length=4, line_break="\n")
  1840. # Assert
  1841. assert "\f" not in paginated
  1842. assert "\n" in paginated
  1843. def test_add_rows() -> None:
  1844. """A table created with multiple add_row calls
  1845. is the same as one created with a single add_rows
  1846. """
  1847. # Arrange
  1848. table1 = PrettyTable(["A", "B", "C"])
  1849. table2 = PrettyTable(["A", "B", "C"])
  1850. table1.add_row([1, 2, 3])
  1851. table1.add_row([4, 5, 6])
  1852. rows = [
  1853. [1, 2, 3],
  1854. [4, 5, 6],
  1855. ]
  1856. # Act
  1857. table2.add_rows(rows)
  1858. # Assert
  1859. assert str(table1) == str(table2)
  1860. def test_autoindex() -> None:
  1861. """Testing that a table with a custom index row is
  1862. equal to the one produced by the function
  1863. .add_autoindex()
  1864. """
  1865. table1 = PrettyTable()
  1866. table1.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
  1867. table1.add_row(["Adelaide", 1295, 1158259, 600.5])
  1868. table1.add_row(["Brisbane", 5905, 1857594, 1146.4])
  1869. table1.add_row(["Darwin", 112, 120900, 1714.7])
  1870. table1.add_row(["Hobart", 1357, 205556, 619.5])
  1871. table1.add_row(["Sydney", 2058, 4336374, 1214.8])
  1872. table1.add_row(["Melbourne", 1566, 3806092, 646.9])
  1873. table1.add_row(["Perth", 5386, 1554769, 869.4])
  1874. table1.add_autoindex(fieldname="Test")
  1875. table2 = PrettyTable()
  1876. table2.field_names = ["Test", "City name", "Area", "Population", "Annual Rainfall"]
  1877. table2.add_row([1, "Adelaide", 1295, 1158259, 600.5])
  1878. table2.add_row([2, "Brisbane", 5905, 1857594, 1146.4])
  1879. table2.add_row([3, "Darwin", 112, 120900, 1714.7])
  1880. table2.add_row([4, "Hobart", 1357, 205556, 619.5])
  1881. table2.add_row([5, "Sydney", 2058, 4336374, 1214.8])
  1882. table2.add_row([6, "Melbourne", 1566, 3806092, 646.9])
  1883. table2.add_row([7, "Perth", 5386, 1554769, 869.4])
  1884. assert str(table1) == str(table2)
  1885. @pytest.fixture(scope="function")
  1886. def unpadded_pt() -> PrettyTable:
  1887. table = PrettyTable(header=False, padding_width=0)
  1888. table.add_row("abc")
  1889. table.add_row("def")
  1890. table.add_row("g..")
  1891. return table
  1892. class TestUnpaddedTable:
  1893. def test_unbordered(self, unpadded_pt: PrettyTable) -> None:
  1894. unpadded_pt.border = False
  1895. result = unpadded_pt.get_string()
  1896. expected = """
  1897. abc
  1898. def
  1899. g..
  1900. """
  1901. assert result.strip() == expected.strip()
  1902. def test_bordered(self, unpadded_pt: PrettyTable) -> None:
  1903. unpadded_pt.border = True
  1904. result = unpadded_pt.get_string()
  1905. expected = """
  1906. +-+-+-+
  1907. |a|b|c|
  1908. |d|e|f|
  1909. |g|.|.|
  1910. +-+-+-+
  1911. """
  1912. assert result.strip() == expected.strip()
  1913. class TestCustomFormatter:
  1914. def test_init_custom_format_is_empty(self) -> None:
  1915. table = PrettyTable()
  1916. assert table.custom_format == {}
  1917. def test_init_custom_format_set_value(self) -> None:
  1918. table = PrettyTable(
  1919. custom_format={"col1": (lambda col_name, value: f"{value:.2}")}
  1920. )
  1921. assert len(table.custom_format) == 1
  1922. def test_init_custom_format_throw_error_is_not_callable(self) -> None:
  1923. with pytest.raises(ValueError) as e:
  1924. PrettyTable(custom_format={"col1": "{:.2}"})
  1925. assert "Invalid value for custom_format.col1. Must be a function." in str(
  1926. e.value
  1927. )
  1928. def test_can_set_custom_format_from_property_setter(self) -> None:
  1929. table = PrettyTable()
  1930. table.custom_format = {"col1": (lambda col_name, value: f"{value:.2}")}
  1931. assert len(table.custom_format) == 1
  1932. def test_set_custom_format_to_none_set_empty_dict(self) -> None:
  1933. table = PrettyTable()
  1934. table.custom_format = None
  1935. assert len(table.custom_format) == 0
  1936. assert isinstance(table.custom_format, dict)
  1937. def test_set_custom_format_invalid_type_throw_error(self) -> None:
  1938. table = PrettyTable()
  1939. with pytest.raises(TypeError) as e:
  1940. table.custom_format = "Some String"
  1941. assert "The custom_format property need to be a dictionary or callable" in str(
  1942. e.value
  1943. )
  1944. def test_use_custom_formatter_for_int(
  1945. self, city_data_prettytable: PrettyTable
  1946. ) -> None:
  1947. city_data_prettytable.custom_format["Annual Rainfall"] = lambda n, v: f"{v:.2f}"
  1948. assert (
  1949. city_data_prettytable.get_string().strip()
  1950. == """
  1951. +-----------+------+------------+-----------------+
  1952. | City name | Area | Population | Annual Rainfall |
  1953. +-----------+------+------------+-----------------+
  1954. | Adelaide | 1295 | 1158259 | 600.50 |
  1955. | Brisbane | 5905 | 1857594 | 1146.40 |
  1956. | Darwin | 112 | 120900 | 1714.70 |
  1957. | Hobart | 1357 | 205556 | 619.50 |
  1958. | Sydney | 2058 | 4336374 | 1214.80 |
  1959. | Melbourne | 1566 | 3806092 | 646.90 |
  1960. | Perth | 5386 | 1554769 | 869.40 |
  1961. +-----------+------+------------+-----------------+
  1962. """.strip()
  1963. )
  1964. def test_custom_format_multi_type(self) -> None:
  1965. table = PrettyTable(["col_date", "col_str", "col_float", "col_int"])
  1966. table.add_row([dt.date(2021, 1, 1), "January", 12345.12345, 12345678])
  1967. table.add_row([dt.date(2021, 2, 1), "February", 54321.12345, 87654321])
  1968. table.custom_format["col_date"] = lambda f, v: v.strftime("%d %b %Y")
  1969. table.custom_format["col_float"] = lambda f, v: f"{v:.3f}"
  1970. table.custom_format["col_int"] = lambda f, v: f"{v:,}"
  1971. assert (
  1972. table.get_string().strip()
  1973. == """
  1974. +-------------+----------+-----------+------------+
  1975. | col_date | col_str | col_float | col_int |
  1976. +-------------+----------+-----------+------------+
  1977. | 01 Jan 2021 | January | 12345.123 | 12,345,678 |
  1978. | 01 Feb 2021 | February | 54321.123 | 87,654,321 |
  1979. +-------------+----------+-----------+------------+
  1980. """.strip()
  1981. )
  1982. def test_custom_format_multi_type_using_on_function(self) -> None:
  1983. table = PrettyTable(["col_date", "col_str", "col_float", "col_int"])
  1984. table.add_row([dt.date(2021, 1, 1), "January", 12345.12345, 12345678])
  1985. table.add_row([dt.date(2021, 2, 1), "February", 54321.12345, 87654321])
  1986. def my_format(col: str, value: Any) -> str:
  1987. if col == "col_date":
  1988. return value.strftime("%d %b %Y")
  1989. if col == "col_float":
  1990. return f"{value:.3f}"
  1991. if col == "col_int":
  1992. return f"{value:,}"
  1993. return str(value)
  1994. table.custom_format = my_format
  1995. assert (
  1996. table.get_string().strip()
  1997. == """
  1998. +-------------+----------+-----------+------------+
  1999. | col_date | col_str | col_float | col_int |
  2000. +-------------+----------+-----------+------------+
  2001. | 01 Jan 2021 | January | 12345.123 | 12,345,678 |
  2002. | 01 Feb 2021 | February | 54321.123 | 87,654,321 |
  2003. +-------------+----------+-----------+------------+
  2004. """.strip()
  2005. )
  2006. class TestRepr:
  2007. def test_default_repr(self, row_prettytable: PrettyTable) -> None:
  2008. assert row_prettytable.__str__() == row_prettytable.__repr__()
  2009. def test_jupyter_repr(self, row_prettytable: PrettyTable) -> None:
  2010. assert row_prettytable._repr_html_() == row_prettytable.get_html_string()
  2011. class TestMinTableWidth:
  2012. @pytest.mark.parametrize(
  2013. "loops, fields, desired_width, border, internal_border",
  2014. [
  2015. (15, ["Test table"], 20, True, False),
  2016. (16, ["Test table"], 21, True, False),
  2017. (18, ["Test table", "Test table 2"], 40, True, False),
  2018. (19, ["Test table", "Test table 2"], 41, True, False),
  2019. (21, ["Test table", "Test col 2", "Test col 3"], 50, True, False),
  2020. (22, ["Test table", "Test col 2", "Test col 3"], 51, True, False),
  2021. (19, ["Test table"], 20, False, False),
  2022. (20, ["Test table"], 21, False, False),
  2023. (25, ["Test table", "Test table 2"], 40, False, False),
  2024. (26, ["Test table", "Test table 2"], 41, False, False),
  2025. (25, ["Test table", "Test col 2", "Test col 3"], 50, False, False),
  2026. (26, ["Test table", "Test col 2", "Test col 3"], 51, False, False),
  2027. (18, ["Test table"], 20, False, True),
  2028. (19, ["Test table"], 21, False, True),
  2029. (23, ["Test table", "Test table 2"], 40, False, True),
  2030. (24, ["Test table", "Test table 2"], 41, False, True),
  2031. (22, ["Test table", "Test col 2", "Test col 3"], 50, False, True),
  2032. (23, ["Test table", "Test col 2", "Test col 3"], 51, False, True),
  2033. ],
  2034. )
  2035. def test_min_table_width(
  2036. self, loops, fields, desired_width, border, internal_border
  2037. ) -> None:
  2038. for col_width in range(loops):
  2039. x = prettytable.PrettyTable()
  2040. x.border = border
  2041. x.preserve_internal_border = internal_border
  2042. x.field_names = fields
  2043. x.add_row(["X" * col_width] + ["" for _ in range(len(fields) - 1)])
  2044. x.min_table_width = desired_width
  2045. t = x.get_string()
  2046. if border is False and internal_border is False:
  2047. assert [len(x) for x in t.split("\n")] == [desired_width, desired_width]
  2048. elif border is False and internal_border is True:
  2049. assert [len(x) for x in t.split("\n")] == [
  2050. desired_width,
  2051. desired_width - 1,
  2052. desired_width,
  2053. ]
  2054. else:
  2055. assert [len(x) for x in t.split("\n")] == [
  2056. desired_width,
  2057. desired_width,
  2058. desired_width,
  2059. desired_width,
  2060. desired_width,
  2061. ]
  2062. class TestMaxTableWidth:
  2063. def test_max_table_width(self) -> None:
  2064. table = PrettyTable()
  2065. table.max_table_width = 5
  2066. table.add_row([0])
  2067. # FIXME: Table is wider than table.max_table_width
  2068. assert (
  2069. table.get_string().strip()
  2070. == """
  2071. +----+
  2072. | Fi |
  2073. +----+
  2074. | 0 |
  2075. +----+
  2076. """.strip()
  2077. )
  2078. def test_max_table_width_wide(self) -> None:
  2079. table = PrettyTable()
  2080. table.max_table_width = 52
  2081. table.add_row(
  2082. [
  2083. 0,
  2084. 0,
  2085. 0,
  2086. 0,
  2087. 0,
  2088. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
  2089. "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam "
  2090. "erat, sed diam voluptua",
  2091. ]
  2092. )
  2093. assert (
  2094. table.get_string().strip()
  2095. == """
  2096. +---+---+---+---+---+------------------------------+
  2097. | F | F | F | F | F | Field 6 |
  2098. +---+---+---+---+---+------------------------------+
  2099. | 0 | 0 | 0 | 0 | 0 | Lorem ipsum dolor sit amet, |
  2100. | | | | | | consetetur sadipscing elitr, |
  2101. | | | | | | sed diam nonumy eirmod |
  2102. | | | | | | tempor invidunt ut labore et |
  2103. | | | | | | dolore magna aliquyam erat, |
  2104. | | | | | | sed diam voluptua |
  2105. +---+---+---+---+---+------------------------------+""".strip()
  2106. )
  2107. def test_max_table_width_wide2(self) -> None:
  2108. table = PrettyTable()
  2109. table.max_table_width = 70
  2110. table.add_row(
  2111. [
  2112. "Lorem",
  2113. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ",
  2114. "ipsum",
  2115. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ",
  2116. "dolor",
  2117. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ",
  2118. ]
  2119. )
  2120. assert (
  2121. table.get_string().strip()
  2122. == """
  2123. +---+-----------------+---+-----------------+---+-----------------+
  2124. | F | Field 2 | F | Field 4 | F | Field 6 |
  2125. +---+-----------------+---+-----------------+---+-----------------+
  2126. | L | Lorem ipsum | i | Lorem ipsum | d | Lorem ipsum |
  2127. | o | dolor sit amet, | p | dolor sit amet, | o | dolor sit amet, |
  2128. | r | consetetur | s | consetetur | l | consetetur |
  2129. | e | sadipscing | u | sadipscing | o | sadipscing |
  2130. | m | elitr, sed diam | m | elitr, sed diam | r | elitr, sed diam |
  2131. +---+-----------------+---+-----------------+---+-----------------+""".strip()
  2132. )
  2133. def test_max_table_width_wide_vrules_frame(self) -> None:
  2134. table = PrettyTable()
  2135. table.max_table_width = 52
  2136. table.vrules = FRAME
  2137. table.add_row(
  2138. [
  2139. 0,
  2140. 0,
  2141. 0,
  2142. 0,
  2143. 0,
  2144. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
  2145. "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam "
  2146. "erat, sed diam voluptua",
  2147. ]
  2148. )
  2149. assert (
  2150. table.get_string().strip()
  2151. == """
  2152. +--------------------------------------------------+
  2153. | F F F F F Field 6 |
  2154. +--------------------------------------------------+
  2155. | 0 0 0 0 0 Lorem ipsum dolor sit amet, |
  2156. | consetetur sadipscing elitr, |
  2157. | sed diam nonumy eirmod |
  2158. | tempor invidunt ut labore et |
  2159. | dolore magna aliquyam erat, |
  2160. | sed diam voluptua |
  2161. +--------------------------------------------------+""".strip()
  2162. )
  2163. def test_max_table_width_wide_vrules_none(self) -> None:
  2164. table = PrettyTable()
  2165. table.max_table_width = 52
  2166. table.vrules = NONE
  2167. table.add_row(
  2168. [
  2169. 0,
  2170. 0,
  2171. 0,
  2172. 0,
  2173. 0,
  2174. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
  2175. "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam "
  2176. "erat, sed diam voluptua",
  2177. ]
  2178. )
  2179. assert (
  2180. table.get_string().strip()
  2181. == """
  2182. ----------------------------------------------------
  2183. F F F F F Field 6
  2184. ----------------------------------------------------
  2185. 0 0 0 0 0 Lorem ipsum dolor sit amet,
  2186. consetetur sadipscing elitr,
  2187. sed diam nonumy eirmod
  2188. tempor invidunt ut labore et
  2189. dolore magna aliquyam erat,
  2190. sed diam voluptua
  2191. ----------------------------------------------------""".strip() # noqa: W291
  2192. )
  2193. class TestRowEndSection:
  2194. def test_row_end_section(self) -> None:
  2195. table = PrettyTable()
  2196. v = 1
  2197. for row in range(4):
  2198. if row % 2 == 0:
  2199. table.add_row(
  2200. [f"value {v}", f"value{v+1}", f"value{v+2}"], divider=True
  2201. )
  2202. else:
  2203. table.add_row(
  2204. [f"value {v}", f"value{v+1}", f"value{v+2}"], divider=False
  2205. )
  2206. v += 3
  2207. table.del_row(0)
  2208. assert (
  2209. table.get_string().strip()
  2210. == """
  2211. +----------+---------+---------+
  2212. | Field 1 | Field 2 | Field 3 |
  2213. +----------+---------+---------+
  2214. | value 4 | value5 | value6 |
  2215. | value 7 | value8 | value9 |
  2216. +----------+---------+---------+
  2217. | value 10 | value11 | value12 |
  2218. +----------+---------+---------+
  2219. """.strip()
  2220. )
  2221. class TestClearing:
  2222. def test_clear_rows(self, row_prettytable: PrettyTable) -> None:
  2223. t = helper_table()
  2224. t.add_row([0, "a", "b", "c"], divider=True)
  2225. t.clear_rows()
  2226. assert t.rows == []
  2227. assert t.dividers == []
  2228. assert t.field_names == ["", "Field 1", "Field 2", "Field 3"]
  2229. def test_clear(self, row_prettytable: PrettyTable) -> None:
  2230. t = helper_table()
  2231. t.add_row([0, "a", "b", "c"], divider=True)
  2232. t.clear()
  2233. assert t.rows == []
  2234. assert t.dividers == []
  2235. assert t.field_names == []
  2236. class TestPreservingInternalBorders:
  2237. def test_internal_border_preserved(self) -> None:
  2238. pt = helper_table()
  2239. pt.border = False
  2240. pt.preserve_internal_border = True
  2241. assert (
  2242. pt.get_string().strip()
  2243. == """
  2244. | Field 1 | Field 2 | Field 3
  2245. ---+---------+---------+---------
  2246. 1 | value 1 | value2 | value3
  2247. 4 | value 4 | value5 | value6
  2248. 7 | value 7 | value8 | value9
  2249. """.strip() # noqa: W291
  2250. )
  2251. def test_internal_border_preserved_latex(self) -> None:
  2252. pt = helper_table()
  2253. pt.border = False
  2254. pt.format = True
  2255. pt.preserve_internal_border = True
  2256. assert pt.get_latex_string().strip() == (
  2257. "\\begin{tabular}{c|c|c|c}\r\n"
  2258. " & Field 1 & Field 2 & Field 3 \\\\\r\n"
  2259. "1 & value 1 & value2 & value3 \\\\\r\n"
  2260. "4 & value 4 & value5 & value6 \\\\\r\n"
  2261. "7 & value 7 & value8 & value9 \\\\\r\n"
  2262. "\\end{tabular}"
  2263. )
  2264. def test_internal_border_preserved_html(self) -> None:
  2265. pt = helper_table()
  2266. pt.format = True
  2267. pt.border = False
  2268. pt.preserve_internal_border = True
  2269. assert (
  2270. pt.get_html_string().strip()
  2271. == """
  2272. <table rules="cols">
  2273. <thead>
  2274. <tr>
  2275. <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th>
  2276. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
  2277. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
  2278. <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
  2279. </tr>
  2280. </thead>
  2281. <tbody>
  2282. <tr>
  2283. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td>
  2284. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td>
  2285. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td>
  2286. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td>
  2287. </tr>
  2288. <tr>
  2289. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td>
  2290. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td>
  2291. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td>
  2292. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td>
  2293. </tr>
  2294. <tr>
  2295. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td>
  2296. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td>
  2297. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td>
  2298. <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td>
  2299. </tr>
  2300. </tbody>
  2301. </table>
  2302. """.strip() # noqa: E501
  2303. )
  2304. class TestGeneralOutput:
  2305. def test_copy(self) -> None:
  2306. # Arrange
  2307. t = helper_table()
  2308. # Act
  2309. t_copy = t.copy()
  2310. # Assert
  2311. assert t.get_string() == t_copy.get_string()
  2312. def test_text(self) -> None:
  2313. t = helper_table()
  2314. assert t.get_formatted_string("text") == t.get_string()
  2315. # test with default arg, too
  2316. assert t.get_formatted_string() == t.get_string()
  2317. # args passed through
  2318. assert t.get_formatted_string(border=False) == t.get_string(border=False)
  2319. def test_csv(self) -> None:
  2320. t = helper_table()
  2321. assert t.get_formatted_string("csv") == t.get_csv_string()
  2322. # args passed through
  2323. assert t.get_formatted_string("csv", border=False) == t.get_csv_string(
  2324. border=False
  2325. )
  2326. def test_json(self) -> None:
  2327. t = helper_table()
  2328. assert t.get_formatted_string("json") == t.get_json_string()
  2329. # args passed through
  2330. assert t.get_formatted_string("json", border=False) == t.get_json_string(
  2331. border=False
  2332. )
  2333. def test_html(self) -> None:
  2334. t = helper_table()
  2335. assert t.get_formatted_string("html") == t.get_html_string()
  2336. # args passed through
  2337. assert t.get_formatted_string("html", border=False) == t.get_html_string(
  2338. border=False
  2339. )
  2340. def test_latex(self) -> None:
  2341. t = helper_table()
  2342. assert t.get_formatted_string("latex") == t.get_latex_string()
  2343. # args passed through
  2344. assert t.get_formatted_string("latex", border=False) == t.get_latex_string(
  2345. border=False
  2346. )
  2347. def test_invalid(self) -> None:
  2348. t = helper_table()
  2349. with pytest.raises(ValueError):
  2350. t.get_formatted_string("pdf")