test_prettytable.py 96 KB

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