Tiny python library with zero dependencies which generates formatted multiline tables in markdown
.
Install via pip as follows:
pip install py-markdown-table
Pass a list
of dict
s where the dict
s must have uniform keys which serve as column headers and the values are expanded to be rows. Simple example with no special formatting:
from py_markdown_table.markdown_table import markdown_table
data = [
{
"Product": "Smartphone",
"Brand": "Apple",
"Price": 999.99
},
{
"Product": "Laptop",
"Brand": "Dell",
"Price": 1299.99
}
]
markdown = markdown_table(data).get_markdown()
print(markdown)
+------------------------+
| Product |Brand| Price |
+----------+-----+-------+
|Smartphone|Apple| 999.99|
+----------+-----+-------+
| Laptop | Dell|1299.99|
+------------------------+
A more comprehensive example showcasing some of the formatting options:
from py_markdown_table.markdown_table import markdown_table
jokes_list = [
{
"joke1": "Why don't scientists trust atoms? Because they make up everything!",
"joke2": "Did you hear about the mathematician who's afraid of negative numbers? He will stop at nothing to avoid them!",
"joke3": "Why don't skeletons fight each other? They don't have the guts!"
},
{
"joke1": "What do you call a snowman with a six-pack? An abdominal snowman!",
"joke2": "Why don't eggs tell jokes? Because they might crack up!",
"joke3": "How does a penguin build its house? Igloos it together!"
}
]
markdown = markdown_table(jokes_list).set_params(padding_width = 3,
padding_weight = 'centerleft',
multiline = {'joke1': 30, 'joke2': 30, 'joke3': 30}
).get_markdown()
+--------------------------------------------------------------------------------------------------------------+
| joke1 | joke2 | joke3 |
+------------------------------------+------------------------------------+------------------------------------+
| Why don't scientists trust atoms? | Did you hear about the | Why don't skeletons fight each |
| Because they make up everything! | mathematician who's afraid of | other? They don't have the guts! |
| | negative numbers? He will stop at | |
| | nothing to avoid them! | |
+------------------------------------+------------------------------------+------------------------------------+
| What do you call a snowman with a | Why don't eggs tell jokes? | How does a penguin build its |
| six-pack? An abdominal snowman! | Because they might crack up! | house? Igloos it together! |
+--------------------------------------------------------------------------------------------------------------+
You can also use pandas dataframes by formatting them as follows:
from py_markdown_table.markdown_table import markdown_table
data = df.to_dict(orient='records')
markdown_table(data).get_markdown()
To add parameters to how the markdown table is formatted, you can use the set_params()
function on a markdown_table
object, i.e. markdown_table(data).set_params(...).get_markdown()
, which allows you to pass the following keyword arguments:
+--------------------------------------------------------------------------------------------------+
| param | type | values | description |
+-----------------------+---------------------+-------------------+--------------------------------+
| row_sep | str | | Row separation strategy using |
| | | | `----` as pattern |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | always | Separate each row |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | topbottom | Separate the top (header) and |
| | | | bottom (last row) of the table |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | markdown | Separate only header from body |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | None | No row separators will be |
| | | | inserted |
+-----------------------+---------------------+-------------------+--------------------------------+
| padding_width | int or | | Allocate padding to all table |
| | dict<str,int> | | cells when passing an int or |
| | | | per-column when passing a dict |
+-----------------------+---------------------+-------------------+--------------------------------+
| padding_weight | str or | | Strategy for allocating |
| | dict<str,str> | | padding within table cells. |
| | | | Per-column when passing a dict |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | left | Aligns the cell's contents to |
| | | | the end of the cell |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | right | Aligns the cell's contents to |
| | | | the beginning of the cell |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | centerleft | Centers cell's contents with |
| | | | extra padding allocated to the |
| | | | beginning of the cell |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | centerright | Centers cell's contents with |
| | | | extra padding allocated to the |
| | | | end of the cell |
+-----------------------+---------------------+-------------------+--------------------------------+
| padding_char | str | | Single character used to fill |
| | | | padding with. Default is a |
| | | | blank space ` `. |
+-----------------------+---------------------+-------------------+--------------------------------+
| newline_char | str | | Character appended to each row |
| | | | to force a newline. Default is |
| | | | `\n` |
+-----------------------+---------------------+-------------------+--------------------------------+
| float_rounding | int | | Integer denoting the precision |
| | | | of cells of `floats` after the |
| | | | decimal point. Default is |
| | | | `None`. |
+-----------------------+---------------------+-------------------+--------------------------------+
| emoji_spacing | str | | Strategy for rendering emojis |
| | | | in tables. Currently only |
| | | | `mono` is supported for |
| | | | monospaced fonts. Default is |
| | | | `None` which disables special |
| | | | handling of emojis. |
+-----------------------+---------------------+-------------------+--------------------------------+
| multiline | dict<Any,int> | | Renders the table with |
| | | | predefined widths by passing a |
| | | | `dict` with `keys` being the |
| | | | column names (e.g. equivalent |
| | | | to those in the passed `data` |
| | | | variable) and `values` -- the |
| | | | `width` of each column as an |
| | | | integer. Note that the width |
| | | | of a column cannot be smaller |
| | | | than the longest contiguous |
| | | | string present in the data. |
+-----------------------+---------------------+-------------------+--------------------------------+
| multiline_strategy | str | | Strategy applied to rendering |
| | | | contents in multiple lines. |
| | | | Possible values are `rows`, |
| | | | `header` or `rows_and_header`. |
| | | | The default value is `rows`. |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | rows | Splits only rows overfilling |
| | | | by the predefined column width |
| | | | as provided in the `multiline` |
| | | | variable |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | header | Splits only the header |
| | | | overfilling by the predefined |
| | | | column width as provided in |
| | | | the `multiline` variable |
+-----------------------+---------------------+-------------------+--------------------------------+
| | | rows_and_header | Splits rows and header |
| | | | overfilling by the predefined |
| | | | column width as provided in |
| | | | the `multiline` variable |
+-----------------------+---------------------+-------------------+--------------------------------+
| multiline_delimiter | str | | Character that will be used to |
| | | | split a cell's contents into |
| | | | multiple rows. The default |
| | | | value is a blank space ` `. |
+-----------------------+---------------------+-------------------+--------------------------------+
| quote | bool | | Wraps the generated markdown |
| | | | table in block quotes |
| | | | ```table```. Default is |
| | | | `True`. |
+--------------------------------------------------------------------------------------------------+
The namespace py_markdown_table.utils
provides the functions count_emojis()
and find_longest_contiguous_strings()
. count_emojis()
detects emojis and their position in a given string, and find_longest_contiguous_strings()
finds the longest continuous strings present in the rows and/or columns of your input data. find_longest_contiguous_strings()
can be useful to figure out the minimal width of each column given a particular data.
markdown_table(data).set_params(row_sep = 'always').get_markdown()
see example
+----------------------------------------+
| title | time | date |seats|
+------------+-----------+---------+-----+
|Vrij Zwemmen|21:30-23:00|Wed 09.12|24/24|
+------------+-----------+---------+-----+
|Vrij Zwemmen|12:00-13:00|Thu 10.12|18/18|
+------------+-----------+---------+-----+
|Vrij zwemmen| 7:30-8:30 |Fri 11.12|18/18|
+------------+-----------+---------+-----+
|Vrij Zwemmen|13:15-14:15|Sat 12.12|18/18|
+----------------------------------------+
markdown_table(data).set_params(row_sep = 'topbottom').get_markdown()
see example
+----------------------------------------+
| title | time | date |seats|
|Vrij Zwemmen|21:30-23:00|Wed 09.12|24/24|
|Vrij Zwemmen|12:00-13:00|Thu 10.12|18/18|
|Vrij zwemmen| 7:30-8:30 |Fri 11.12|18/18|
|Vrij Zwemmen|13:15-14:15|Sat 12.12|18/18|
+----------------------------------------+
markdown_table(data).set_params(row_sep = 'markdown').get_markdown()
see example
| title | time | date |seats|
|------------|-----------|---------|-----|
|Vrij Zwemmen|21:30-23:00|Wed 09.12|24/24|
|Vrij Zwemmen|12:00-13:00|Thu 10.12|18/18|
|Vrij zwemmen| 7:30-8:30 |Fri 11.12|18/18|
|Vrij Zwemmen|13:15-14:15|Sat 12.12|18/18|
markdown_table(data).set_params(row_sep = 'markdown', quote = False).get_markdown()
see example
title | time | date | seats |
---|---|---|---|
Vrij Zwemmen | 21:30-23:00 | Wed 09.12 | 24/24 |
Vrij Zwemmen | 12:00-13:00 | Thu 10.12 | 18/18 |
Vrij zwemmen | 7:30-8:30 | Fri 11.12 | 18/18 |
Vrij Zwemmen | 13:15-14:15 | Sat 12.12 | 18/18 |
markdown_table(data).set_params(row_sep = 'topbottom', padding_width = 5, padding_weight = 'left').get_markdown()
see example
+------------------------------------------------------------+
| title| time| date| seats|
| Vrij Zwemmen| 21:30-23:00| Wed 09.12| 24/24|
| Vrij Zwemmen| 12:00-13:00| Thu 10.12| 18/18|
| Vrij zwemmen| 7:30-8:30| Fri 11.12| 18/18|
| Vrij Zwemmen| 13:15-14:15| Sat 12.12| 18/18|
+------------------------------------------------------------+
markdown_table(data).set_params(row_sep = 'topbottom', padding_width = 5, padding_weight = 'centerright').get_markdown()
see example
+------------------------------------------------------------+
| title | time | date | seats |
| Vrij Zwemmen | 21:30-23:00 | Wed 09.12 | 24/24 |
| Vrij Zwemmen | 12:00-13:00 | Thu 10.12 | 18/18 |
| Vrij zwemmen | 7:30-8:30 | Fri 11.12 | 18/18 |
| Vrij Zwemmen | 13:15-14:15 | Sat 12.12 | 18/18 |
+------------------------------------------------------------+
markdown_table(data).set_params(row_sep = 'always', padding_width = 5, padding_weight = 'centerright', padding_char = '.').get_markdown()
see example
+------------------------------------------------------------+
|......title......|......time......|.....date.....|..seats...|
+-----------------+----------------+--------------+----------+
|..Vrij Zwemmen...|..21:30-23:00...|..Wed 09.12...|..24/24...|
+-----------------+----------------+--------------+----------+
|..Vrij Zwemmen...|..12:00-13:00...|..Thu 10.12...|..18/18...|
+-----------------+----------------+--------------+----------+
|..Vrij zwemmen...|...7:30-8:30....|..Fri 11.12...|..18/18...|
+-----------------+----------------+--------------+----------+
|..Vrij Zwemmen...|..13:15-14:15...|..Sat 12.12...|..18/18...|
+------------------------------------------------------------+
markdown_table(data).set_params(row_sep = 'always', padding_width = 5, padding_weight = 'centerright', padding_char = '.').get_markdown()
see example
+------------------------------------------------------------+
|......title......|......time......|.....date.....|..seats...|
+-----------------+----------------+--------------+----------+
|..Vrij Zwemmen...|..21:30-23:00...|..Wed 09.12...|..24/24...|
+-----------------+----------------+--------------+----------+
|..Vrij Zwemmen...|..12:00-13:00...|..Thu 10.12...|..18/18...|
+-----------------+----------------+--------------+----------+
|..Vrij zwemmen...|...7:30-8:30....|..Fri 11.12...|..18/18...|
+-----------------+----------------+--------------+----------+
|..Vrij Zwemmen...|..13:15-14:15...|..Sat 12.12...|..18/18...|
+------------------------------------------------------------+
markdown_table(data).set_params(row_sep = 'always', padding_width = {"title": 2, "time": 4, "date": 3, "seats": 1}, padding_weight = {"title": "left", "time": "right", "date": "centerleft", "seats": "centerright"}).get_markdown()
see example
+--------------------------------------------------+
| title|time | date |seats |
+--------------+---------------+------------+------+
| Vrij Zwemmen|21:30-23:00 | Wed 09.12 |24/24 |
+--------------+---------------+------------+------+
| Vrij Zwemmen|12:00-13:00 | Thu 10.12 |18/18 |
+--------------+---------------+------------+------+
| Vrij Zwemmen|7:30-8:30 | Fri 11.12 |18/18 |
+--------------+---------------+------------+------+
| Vrij Zwemmen|13:15-14:15 | Sat 12.12 |18/18 |
+--------------------------------------------------+
markdown_table(data).set_params(row_sep = 'always', padding_width = {"A": 2, "B": 4, "C": 3}, padding_weight = {"A": "left", "B": "right", "C": "centerleft"}).get_markdown()
see example
+-----------------------------------------------------------------------+
| A|B | C |
+-----------------------------+-------------------------------+---------+
| row1_A and additional stuff|row1_B | row1_C |
+-----------------------------+-------------------------------+---------+
| row2_A|row2_B and additional stuff | row2_C |
+-----------------------------+-------------------------------+---------+
| row3_A|row3_B | row3_C |
+-----------------------------------------------------------------------+
markdown_table(data).set_params(padding_width = 0, padding_weight = "centerleft", multiline = {"A": 25, "B": 12, "C": 9}).get_markdown()
see example
+------------------------------------------------+
| A | B | C |
+-------------------------+------------+---------+
| row1_A and additional | row1_B | row1_C |
| stuff | | |
+-------------------------+------------+---------+
| row2_A | row2_B and | row2_C |
| | additional | |
| | stuff | |
+-------------------------+------------+---------+
| row3_A | row3_B | row3_C |
+------------------------------------------------+
markdown_table(data).set_params(padding_width = 2, padding_weight = "centerleft", multiline = {"A": 25, "B": 12, "C": 9}).get_markdown())
see example
+------------------------------------------------------------+
| A | B | C |
+-----------------------------+----------------+-------------+
| row1_A and additional stuff | row1_B | row1_C |
+-----------------------------+----------------+-------------+
| row2_A | row2_B and | row2_C |
| | additional | |
| | stuff | |
+-----------------------------+----------------+-------------+
| row3_A | row3_B | row3_C |
+------------------------------------------------------------+
markdown_table(data).set_params(row_sep = "always", multiline = {"those are multi rows": 5}, multiline_strategy = "rows_and_header").get_markdown()
see example
+-----+
|those|
| are |
|multi|
| rows|
+-----+
| yes |
| they|
| are |
+-----+
| no |
| they|
| are |
| not |
+-----+
markdown_table(data).set_params(row_sep = "topbottom", emoji_spacing = "mono", multiline = {"title that is maybe too long": 7, "time": 11, "date": 5, "seats": 5,}, multiline_strategy = "rows_and_header").get_markdown()
see example
*Note:* Github's markdown preview does not render emojis as two whole characters, hence the slight offsets in cells containing emojis.+-------------------------------+
| title | time | date|seats|
|that is| | | |
| maybe | | | |
| too | | | |
| long | | | |
| Vrij |21:30-23:00| 😊 |24/24|
|Zwemmen| | | |
| Vrij |12:00-13:00| Thu |18/18|
|Zwemmen| |10.12| |
| Vrij | 7:30-8:30 | Fri | 😊 |
|Zwemmen| |11.12|🌍 🎉|
| Vrij |13:15-14:15| Sat |20/20|
|Zwemmen| |12.12| |
| Vrij | 7:30-8:30 | Fri | asd |
|Zwemmen| |11.12| 😊-|
| | | | 🌍: |
| | | | 🎉 |
|Zwemmen|13:15-14:15| Sat |20/20|
| | |12.12| |
+-------------------------------+
Below is an example from a monospaced terminal, where the table is rendered correctly.
The table below provide some benchmark results, evaluating the performance on data containing incrementally larger number of columns
, rows
, and characters in each table cell (i.e. cell_size
). You can benchmark it on your own system using the script contained within py_markdown_table/utils/benchmark.py
. Generally, reasonably-sized tables intended to be read by a human can be generated within a millisecond.
see benchmark
+-----------------------------------------------+
| parameters |Multiline| speed |
+------------------+---------+------------------+
| columns: 2 | False | 0.000000 ms |
| rows: 10 | | |
| cell_size: 5 | | |
+------------------+---------+------------------+
| columns: 4 | False | 0.000000 ms |
| rows: 40 | | |
| cell_size: 20 | | |
+------------------+---------+------------------+
| columns: 8 | False | 6.999756 ms |
| rows: 160 | | |
| cell_size: 80 | | |
+------------------+---------+------------------+
| columns: 16 | False | 1173.794678 ms |
| rows: 640 | | |
| cell_size: 320 | | |
+------------------+---------+------------------+
| columns: 2 | True | 0.000000 ms |
| rows: 10 | | |
| cell_size: 5 | | |
+------------------+---------+------------------+
| columns: 4 | True | 0.996338 ms |
| rows: 40 | | |
| cell_size: 20 | | |
+------------------+---------+------------------+
| columns: 8 | True | 16.038330 ms |
| rows: 160 | | |
| cell_size: 80 | | |
+------------------+---------+------------------+
| columns: 16 | True | 1448.473633 ms |
| rows: 640 | | |
| cell_size: 320 | | |
+-----------------------------------------------+