Table¶
Click Extra provides a way to render tables in the terminal.
Tip
The selected --table-format value and the --sort-by priority list are published on ctx.meta as TABLE_FORMAT and SORT_BY. See the available keys table to read them from your own callbacks.
Here how to use the standalone table rendering option decorator:
import click
from click_extra import pass_context, table_format_option, style, Color
@click.command
@table_format_option
@pass_context
def table_command(ctx):
headers = ("Day", "Temperature")
data = (
(1, 42.9),
("2", None),
(style("Friday", fg=Color.blue), style("Hot 🥵", fg=Color.red, bold=True)),
)
ctx.print_table(data, headers)
As you can see above, this option registers a ready-to-use print_table() method to the context object.
The default help message for this option list all available table formats:
$ table --help
Usage: table [OPTIONS]
Options:
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables.
--help Show this message and exit.
So you can use the --table-format option to change the table format:
$ table --table-format fancy-outline
╒════════╤═════════════╕
│ Day │ Temperature │
╞════════╪═════════════╡
│ 1 │ 42.9 │
│ 2 │ │
│ Friday │ Hot 🥵 │
╘════════╧═════════════╛
$ table --table-format asciidoc
[cols="<8,<13",options="header"]
|====
| Day | Temperature
| 1 | 42.9
| 2 |
| Friday | Hot 🥵
|====
Tip
This example has been selected so you can see how print_table() handles:
Mixed data types (integers, floats,
None, strings)ANSI color codes (added with the
click_extra.style()function)Unicode characters (like the emojis)
Hint
There’s another method called render_table() that is registered in the context alongside print_table().
It works the same way, but instead of printing the table to the console, it returns the rendered table as a string.
Table formats¶
Table formats are aggregated from these sources:
Python’s
csvmodule from the standard libraryPython’s
jsonmodule from the standard libraryhjson(requires the[hjson]extra)tomlkit(requires the[toml]extra)xmltodict(requires the[xml]extra)PyYAML(requires the[yaml]extra)
They’re divided in 2 categories:
Formats that produce plain text output (like ASCII tables, grid tables, etc.) and are often composed of Unicode box-drawing characters, to be displayed in a terminal.
Formats that produce markup language output (like HTML, Markdown, LaTeX, etc.) and are expected to be rendered by a supporting viewer. This category also includes CSV, TSV, and structured serialization formats (HJSON, JSON, JSON5, JSONC, TOML, XML, YAML), which are plain text but meant to be processed by other tools.
Tip
The default rounded-outline format draws its borders with Unicode box-drawing characters (│, ╭, ─, …) and aligns columns with padding. A screen reader announces those separators and the padding gaps as noise, and the column alignment is lost entirely once whitespace is collapsed.
For a screen-reader-friendly rendering, use --table-format plain: it keeps the tabular layout with no borders. For output that another program will parse, use a machine-readable format like csv. The --accessible flag bundles --table-format plain with --no-color in a single switch.
Format ID |
Description |
Implementation |
Markup |
|---|---|---|---|
|
Compact table with single-space column separators and no borders |
Click Extra |
❌ |
|
|
✅ |
|
|
|
✅ |
|
|
CSV with Excel dialect |
|
✅ |
|
CSV with Excel tab dialect |
|
✅ |
|
CSV with Unix dialect |
|
✅ |
|
Double-line grid table |
|
❌ |
|
Double-line outline table |
|
❌ |
|
Grid with Unicode box-drawing characters |
|
❌ |
|
Outline with Unicode box-drawing characters |
|
❌ |
|
|
✅ |
|
|
Grid table with ASCII characters, also supported by Pandoc and reStructuredText |
|
❌ |
|
Heavy-line grid table |
|
❌ |
|
Heavy-line outline table |
|
❌ |
|
HJSON array of objects |
✅ |
|
|
|
✅ |
|
|
|
✅ |
|
|
JSON array of objects |
|
✅ |
|
Alias for |
|
✅ |
|
Alias for |
|
✅ |
|
|
✅ |
|
|
|
✅ |
|
|
|
✅ |
|
|
LaTeX table without escaping |
|
✅ |
|
|
✅ |
|
|
Mixed-line grid table |
|
❌ |
|
Mixed-line outline table |
|
❌ |
|
|
✅ |
|
|
|
✅ |
|
|
Simple outline table |
|
❌ |
|
|
✅ |
|
|
Plain text, no formatting |
|
❌ |
|
Presto SQL output style |
|
❌ |
|
Pretty ASCII table |
|
❌ |
|
PostgreSQL output style |
|
❌ |
|
Rounded grid table |
|
❌ |
|
Rounded outline table |
|
❌ |
|
|
✅ |
|
|
Simple table with spaces, also supported by Pandoc |
|
❌ |
|
Simple grid table |
|
❌ |
|
Simple outline table |
|
❌ |
|
|
✅ |
|
|
TOML array of tables |
✅ |
|
|
|
✅ |
|
|
HTML table without escaping |
|
✅ |
|
Vertical table layout |
|
❌ |
|
XML document |
✅ |
|
|
YAML sequence of mappings |
✅ |
|
|
|
✅ |
Colors and styles¶
Cells and headers can carry ANSI styles, produced by click_extra.style() or a theme. What happens to these codes depends on the format family:
Plain-text formats keep them raw. The final
echo()call then honors the global colorization settings, so styles show on a capable terminal and are stripped when piped or under--no-color.Markup formats able to express styles natively translate them to the format’s own styling markup. This is the default: pass
--no-color(or setNO_COLOR) to get plain markup instead.All other markup formats strip them from cell values before rendering, to avoid leaking escape sequences into structured content. Force
--colorto keep the raw ANSI codes anyway.
The formats translating ANSI codes to native styling, and the markup they produce:
Format ID |
Native styling markup |
|---|---|
|
Inline-CSS |
|
|
|
|
|
Inline-CSS |
|
|
Every other markup format keeps stripping ANSI codes. The verdict, format by format:
Format ID |
Why ANSI codes are stripped |
|---|---|
|
No portable inline styling: color roles need a custom stylesheet, and |
|
Data interchange formats, with no concept of styling |
|
GitHub sanitizes inline |
|
Structured serialization formats meant for programmatic consumption: styling is presentation, not data |
|
MoinMoin wiki markup has no standard inline color syntax, and embedded HTML is disabled by default |
|
Org-mode has emphasis markers but no inline color markup |
|
reStructuredText needs custom roles backed by a stylesheet for inline color; there is no standard inline syntax |
|
Undocumented by JetBrains, and scheduled for removal in python-tabulate |
Here is the translation at work: the blue Friday and bold red Hot 🥵 cells of the example command come out as self-contained HTML:
$ table --table-format unsafehtml
<table>
<thead>
<tr><th>Day </th><th>Temperature</th></tr>
</thead>
<tbody>
<tr><td>1 </td><td>42.9 </td></tr>
<tr><td>2 </td><td> </td></tr>
<tr><td><span style="color: blue">Friday</span></td><td><span style="color: red; font-weight: bold">Hot 🥵</span> </td></tr>
</tbody>
</table>
While the same cells are stripped down to plain text in a format without styling support:
$ table --table-format github
| Day | Temperature |
| :----- | :---------- |
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
Hint
The translation maps each ANSI attribute to its closest equivalent, and drops those the target cannot express (like blink in CSS, or backgrounds in Jira markup). Colors keep their terminal semantics: a white cell on a white page renders invisible, as it would on a white terminal. The underlying converters are also available as standalone functions: see ansi_to_html() and friends.
Tip
Use the built-in demo subcommands to verify how ANSI codes are handled by each format:
$ uvx click-extra --table-format unsafehtml styles
$ uvx click-extra --table-format json colors
The first command renders the whole style matrix as a colored HTML page you can redirect to a file and open in a browser.
$ table --table-format aligned
Day Temperature
1 42.9
2
Friday Hot 🥵
$ table --table-format asciidoc
[cols="<8,<13",options="header"]
|====
| Day | Temperature
| 1 | 42.9
| 2 |
| Friday | Hot 🥵
|====
$ table --table-format csv
Day,Temperature
1,42.9
2,
Friday,Hot 🥵
$ table --table-format csv-excel
Day,Temperature
1,42.9
2,
Friday,Hot 🥵
$ table --table-format csv-excel-tab
Day Temperature
1 42.9
2
Friday Hot 🥵
$ table --table-format csv-unix
"Day","Temperature"
"1","42.9"
"2",""
"Friday","Hot 🥵"
$ table --table-format double-grid
╔════════╦═════════════╗
║ Day ║ Temperature ║
╠════════╬═════════════╣
║ 1 ║ 42.9 ║
╠════════╬═════════════╣
║ 2 ║ ║
╠════════╬═════════════╣
║ Friday ║ Hot 🥵 ║
╚════════╩═════════════╝
$ table --table-format double-outline
╔════════╦═════════════╗
║ Day ║ Temperature ║
╠════════╬═════════════╣
║ 1 ║ 42.9 ║
║ 2 ║ ║
║ Friday ║ Hot 🥵 ║
╚════════╩═════════════╝
$ table --table-format fancy-grid
╒════════╤═════════════╕
│ Day │ Temperature │
╞════════╪═════════════╡
│ 1 │ 42.9 │
├────────┼─────────────┤
│ 2 │ │
├────────┼─────────────┤
│ Friday │ Hot 🥵 │
╘════════╧═════════════╛
$ table --table-format fancy-outline
╒════════╤═════════════╕
│ Day │ Temperature │
╞════════╪═════════════╡
│ 1 │ 42.9 │
│ 2 │ │
│ Friday │ Hot 🥵 │
╘════════╧═════════════╛
$ table --table-format github
| Day | Temperature |
| :----- | :---------- |
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
$ table --table-format grid
+--------+-------------+
| Day | Temperature |
+========+=============+
| 1 | 42.9 |
+--------+-------------+
| 2 | |
+--------+-------------+
| Friday | Hot 🥵 |
+--------+-------------+
$ table --table-format heavy-grid
┏━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Day ┃ Temperature ┃
┣━━━━━━━━╋━━━━━━━━━━━━━┫
┃ 1 ┃ 42.9 ┃
┣━━━━━━━━╋━━━━━━━━━━━━━┫
┃ 2 ┃ ┃
┣━━━━━━━━╋━━━━━━━━━━━━━┫
┃ Friday ┃ Hot 🥵 ┃
┗━━━━━━━━┻━━━━━━━━━━━━━┛
$ table --table-format heavy-outline
┏━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Day ┃ Temperature ┃
┣━━━━━━━━╋━━━━━━━━━━━━━┫
┃ 1 ┃ 42.9 ┃
┃ 2 ┃ ┃
┃ Friday ┃ Hot 🥵 ┃
┗━━━━━━━━┻━━━━━━━━━━━━━┛
$ table --table-format hjson
[
{
Day: 1
Temperature: 42.9
}
{
Day: "2"
Temperature: null
}
{
Day: Friday
Temperature: Hot 🥵
}
]
$ table --table-format html
<table>
<thead>
<tr><th>Day </th><th>Temperature</th></tr>
</thead>
<tbody>
<tr><td>1 </td><td>42.9 </td></tr>
<tr><td>2 </td><td> </td></tr>
<tr><td><span style="color: blue">Friday</span></td><td><span style="color: red; font-weight: bold">Hot 🥵</span> </td></tr>
</tbody>
</table>
$ table --table-format jira
|| Day || Temperature ||
| 1 | 42.9 |
| 2 | |
| {color:blue}Friday{color} | {color:red}*Hot 🥵*{color} |
$ table --table-format json
[
{
"Day": 1,
"Temperature": 42.9
},
{
"Day": "2",
"Temperature": null
},
{
"Day": "Friday",
"Temperature": "Hot 🥵"
}
]
$ table --table-format json5
[
{
"Day": 1,
"Temperature": 42.9
},
{
"Day": "2",
"Temperature": null
},
{
"Day": "Friday",
"Temperature": "Hot 🥵"
}
]
$ table --table-format jsonc
[
{
"Day": 1,
"Temperature": 42.9
},
{
"Day": "2",
"Temperature": null
},
{
"Day": "Friday",
"Temperature": "Hot 🥵"
}
]
$ table --table-format latex
\begin{tabular}{ll}
\hline
Day & Temperature \\
\hline
1 & 42.9 \\
2 & \\
\textcolor{blue}{Friday} & \textcolor{red}{\textbf{Hot 🥵}} \\
\hline
\end{tabular}
$ table --table-format latex-booktabs
\begin{tabular}{ll}
\toprule
Day & Temperature \\
\midrule
1 & 42.9 \\
2 & \\
\textcolor{blue}{Friday} & \textcolor{red}{\textbf{Hot 🥵}} \\
\bottomrule
\end{tabular}
$ table --table-format latex-longtable
\begin{longtable}{ll}
\hline
Day & Temperature \\
\hline
\endhead
1 & 42.9 \\
2 & \\
\textcolor{blue}{Friday} & \textcolor{red}{\textbf{Hot 🥵}} \\
\hline
\end{longtable}
$ table --table-format latex-raw
\begin{tabular}{ll}
\hline
Day & Temperature \\
\hline
1 & 42.9 \\
2 & \\
\textcolor{blue}{Friday} & \textcolor{red}{\textbf{Hot 🥵}} \\
\hline
\end{tabular}
$ table --table-format mediawiki
{| class="wikitable" style="text-align: left;"
|+ <!-- caption -->
|-
! Day !! Temperature
|-
| 1 || 42.9
|-
| 2 ||
|-
| <span style="color: blue">Friday</span> || <span style="color: red; font-weight: bold">Hot 🥵</span>
|}
$ table --table-format mixed-grid
┍━━━━━━━━┯━━━━━━━━━━━━━┑
│ Day │ Temperature │
┝━━━━━━━━┿━━━━━━━━━━━━━┥
│ 1 │ 42.9 │
├────────┼─────────────┤
│ 2 │ │
├────────┼─────────────┤
│ Friday │ Hot 🥵 │
┕━━━━━━━━┷━━━━━━━━━━━━━┙
$ table --table-format mixed-outline
┍━━━━━━━━┯━━━━━━━━━━━━━┑
│ Day │ Temperature │
┝━━━━━━━━┿━━━━━━━━━━━━━┥
│ 1 │ 42.9 │
│ 2 │ │
│ Friday │ Hot 🥵 │
┕━━━━━━━━┷━━━━━━━━━━━━━┙
$ table --table-format moinmoin
|| ''' Day ''' || ''' Temperature ''' ||
|| 1 || 42.9 ||
|| 2 || ||
|| Friday || Hot 🥵 ||
$ table --table-format orgtbl
| Day | Temperature |
|--------+-------------|
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
$ table --table-format outline
+--------+-------------+
| Day | Temperature |
+========+=============+
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
+--------+-------------+
$ table --table-format pipe
| Day | Temperature |
| :----- | :---------- |
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
$ table --table-format plain
Day Temperature
1 42.9
2
Friday Hot 🥵
$ table --table-format presto
Day | Temperature
--------+-------------
1 | 42.9
2 |
Friday | Hot 🥵
$ table --table-format pretty
+--------+-------------+
| Day | Temperature |
+--------+-------------+
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
+--------+-------------+
$ table --table-format psql
+--------+-------------+
| Day | Temperature |
|--------+-------------|
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
+--------+-------------+
$ table --table-format rounded-grid
╭────────┬─────────────╮
│ Day │ Temperature │
├────────┼─────────────┤
│ 1 │ 42.9 │
├────────┼─────────────┤
│ 2 │ │
├────────┼─────────────┤
│ Friday │ Hot 🥵 │
╰────────┴─────────────╯
$ table --table-format rounded-outline
╭────────┬─────────────╮
│ Day │ Temperature │
├────────┼─────────────┤
│ 1 │ 42.9 │
│ 2 │ │
│ Friday │ Hot 🥵 │
╰────────┴─────────────╯
$ table --table-format rst
====== ===========
Day Temperature
====== ===========
1 42.9
2
Friday Hot 🥵
====== ===========
$ table --table-format simple
Day Temperature
------ -----------
1 42.9
2
Friday Hot 🥵
$ table --table-format simple-grid
┌────────┬─────────────┐
│ Day │ Temperature │
├────────┼─────────────┤
│ 1 │ 42.9 │
├────────┼─────────────┤
│ 2 │ │
├────────┼─────────────┤
│ Friday │ Hot 🥵 │
└────────┴─────────────┘
$ table --table-format simple-outline
┌────────┬─────────────┐
│ Day │ Temperature │
├────────┼─────────────┤
│ 1 │ 42.9 │
│ 2 │ │
│ Friday │ Hot 🥵 │
└────────┴─────────────┘
$ table --table-format textile
|_. Day |_. Temperature |
|<. 1 |<. 42.9 |
|<. 2 |<. |
|<. %{color: blue}Friday% |<. %{color: red; font-weight: bold}Hot 🥵% |
$ table --table-format toml
[[record]]
Day = 1
Temperature = 42.9
[[record]]
Day = "2"
[[record]]
Day = "Friday"
Temperature = "Hot 🥵"
$ table --table-format tsv
Day Temperature
1 42.9
2
Friday Hot 🥵
$ table --table-format unsafehtml
<table>
<thead>
<tr><th>Day </th><th>Temperature</th></tr>
</thead>
<tbody>
<tr><td>1 </td><td>42.9 </td></tr>
<tr><td>2 </td><td> </td></tr>
<tr><td><span style="color: blue">Friday</span></td><td><span style="color: red; font-weight: bold">Hot 🥵</span> </td></tr>
</tbody>
</table>
$ table --table-format vertical
***************************[ 1. row ]***************************
Day | 1
Temperature | 42.9
***************************[ 2. row ]***************************
Day | 2
Temperature |
***************************[ 3. row ]***************************
Day | Friday
Temperature | Hot 🥵
$ table --table-format xml
<records>
<record>
<Day>1</Day>
<Temperature>42.9</Temperature>
</record>
<record>
<Day>2</Day>
</record>
<record>
<Day>Friday</Day>
<Temperature>Hot 🥵</Temperature>
</record>
</records>
$ table --table-format yaml
- Day: 1
Temperature: 42.9
- Day: '2'
Temperature: null
- Day: Friday
Temperature: Hot 🥵
$ table --table-format youtrack
|| Day || Temperature ||
| 1 | 42.9 |
| 2 | |
| Friday | Hot 🥵 |
Get table format¶
You can get the ID of the current table format from the context:
import click
from click_extra import echo, pass_context, table_format_option
@click.command
@table_format_option
@pass_context
def vanilla_command(ctx):
format_id = ctx.meta["click_extra.table_format"]
echo(f"Table format: {format_id}")
data = ((1, 87), (2, 80), (3, 79))
headers = ("day", "temperature")
ctx.print_table(data, headers)
$ vanilla --table-format fancy-outline
Table format: fancy-outline
╒═════╤═════════════╕
│ day │ temperature │
╞═════╪═════════════╡
│ 1 │ 87 │
│ 2 │ 80 │
│ 3 │ 79 │
╘═════╧═════════════╛
Data serialization¶
print_data() and serialize_data() handle arbitrary data structures (nested dicts, lists, scalars), unlike print_table() which expects tabular rows and headers. They support the structured serialization formats: JSON, HJSON, YAML, TOML, and XML.
from click_extra import command, pass_context, table_format_option
from click_extra.table import print_data
@command
@table_format_option
@pass_context
def data_command(ctx):
"""Serialize nested data."""
data = {
"city": "Paris",
"population": 2161000,
"landmarks": ["Eiffel Tower", "Louvre", "Notre-Dame"],
}
table_format = ctx.meta["click_extra.table_format"]
print_data(data, table_format)
$ data --table-format json
{
"city": "Paris",
"population": 2161000,
"landmarks": [
"Eiffel Tower",
"Louvre",
"Notre-Dame"
]
}
/home/runner/work/click-extra/click-extra/click_extra/commands.py:491: UserWarning: The parameter --table-format is used more than once. Remove its duplicate as parameters should be unique.
self._resolve_color_eagerly(ctx, args)
/home/runner/work/click-extra/click-extra/.venv/lib/python3.14/site-packages/click/core.py:1307: UserWarning: The parameter --table-format is used more than once. Remove its duplicate as parameters should be unique.
parser = self.make_parser(ctx)
/home/runner/work/click-extra/click-extra/.venv/lib/python3.14/site-packages/cloup/constraints/_support.py:183: UserWarning: The parameter --table-format is used more than once. Remove its duplicate as parameters should be unique.
args = super().parse_args(ctx, args) # type: ignore
$ data --table-format yaml
city: Paris
landmarks:
- Eiffel Tower
- Louvre
- Notre-Dame
population: 2161000
serialize_data() returns the serialized string instead of printing it:
Sorted tables¶
The @sort_by_option decorator adds a --sort-by CLI option whose choices are derived from column definitions. Column definitions are (label, column_id) tuples or ColumnSpec instances. Columns with column_id=None are displayed but not offered as sort choices.
The option can be repeated to define a multi-column sort priority: --sort-by name --sort-by age sorts by name first, then breaks ties by age.
When active, SortByOption replaces ctx.print_table with a sorted variant, so the command body doesn’t need any sorting logic.
from click_extra import command, pass_context, sort_by_option
@command
@sort_by_option(
("Fruit", "fruit"),
("Count", "count"),
("Notes", None),
)
@pass_context
def inventory(ctx):
"""Sortable fruit inventory."""
headers = ("Fruit", "Count", "Notes")
data = [
["Cherry", "50", "seasonal"],
["Apple", "120", ""],
["Banana", "80", "organic"],
]
ctx.print_table(data, headers)
$ inventory --help
Usage: inventory [OPTIONS]
Sortable fruit inventory.
Options:
--time / --no-time Measure and print elapsed execution time.
[default: no-time]
--config CONFIG_PATH Location of the configuration file. Supports
local path with glob patterns or remote URL.
[default: ~/.config/inventory/{*.toml,*.yaml,*.ym
l,*.json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,pypr
oject.toml}]
--no-config Ignore all configuration files and only use
command line parameters and environment
variables.
--validate-config FILE Validate the configuration file and exit.
--export-config FORMAT Export the configuration in the selected format
to <stdout>, then exit.
--accessible Accessibility mode: disable colors and render
tables in a plain, screen-reader-friendly format.
--color [auto|always|never] Colorize the output. A bare --color is the same
as --color=always. [default: auto]
--no-color Disable colorization (alias of --color=never).
--progress / --no-progress Show progress indicators during long operations.
Disabled for non-interactive output (pipes, dumb
terminals, CI) and by --accessible. [default:
progress]
--theme [dark|dracula|light|manpage|monokai|nord|solarized_dark]
Color theme used for help screens. [default:
dark]
--show-params Show all CLI parameters, their provenance,
defaults and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-
outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
-q, --quiet Decrease the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
--tree Show the tree of nested subcommands and exit.
--man Show the command's man page (roff) and exit.
--version Show the version and exit.
--sort-by [fruit|count] Sort table by this column. Repeat to set
priority. [default: fruit]
-h, --help Show this message and exit.
$ inventory --table-format rounded-outline --sort-by fruit
╭────────┬───────┬──────────╮
│ Fruit │ Count │ Notes │
├────────┼───────┼──────────┤
│ Apple │ 120 │ │
│ Banana │ 80 │ organic │
│ Cherry │ 50 │ seasonal │
╰────────┴───────┴──────────╯
$ inventory --table-format rounded-outline --sort-by count
╭────────┬───────┬──────────╮
│ Fruit │ Count │ Notes │
├────────┼───────┼──────────┤
│ Apple │ 120 │ │
│ Cherry │ 50 │ seasonal │
│ Banana │ 80 │ organic │
╰────────┴───────┴──────────╯
Repeating --sort-by sets multi-column priority. Here, rows are sorted by count first, then ties broken by fruit name:
$ inventory --table-format rounded-outline --sort-by count --sort-by fruit
╭────────┬───────┬──────────╮
│ Fruit │ Count │ Notes │
├────────┼───────┼──────────┤
│ Apple │ 120 │ │
│ Cherry │ 50 │ seasonal │
│ Banana │ 80 │ organic │
╰────────┴───────┴──────────╯
To keep --sort-by and --columns in sync, pass the same ColumnSpec registry to both via columns=. A single source of truth then drives column selection, column order and sort choices:
from click_extra import columns_option, command, pass_context, sort_by_option
from click_extra.table import ColumnSpec
COLUMNS = (
ColumnSpec("fruit", "Fruit"),
ColumnSpec("count", "Count"),
)
@command
@columns_option(columns=COLUMNS)
@sort_by_option(columns=COLUMNS)
@pass_context
def stock(ctx):
"""Fruit stock, sortable and column-selectable from one registry."""
data = [["Cherry", "50"], ["Apple", "120"], ["Banana", "80"]]
ctx.print_table(data, [col.label for col in COLUMNS])
$ stock --help
Usage: stock [OPTIONS]
Fruit stock, sortable and column-selectable from one registry.
Options:
--time / --no-time Measure and print elapsed execution time.
[default: no-time]
--config CONFIG_PATH Location of the configuration file. Supports
local path with glob patterns or remote URL.
[default: ~/.config/stock/{*.toml,*.yaml,*.yml,*.
json,*.json5,*.jsonc,*.hjson,*.ini,*.xml,pyprojec
t.toml}]
--no-config Ignore all configuration files and only use
command line parameters and environment
variables.
--validate-config FILE Validate the configuration file and exit.
--export-config FORMAT Export the configuration in the selected format
to <stdout>, then exit.
--accessible Accessibility mode: disable colors and render
tables in a plain, screen-reader-friendly format.
--color [auto|always|never] Colorize the output. A bare --color is the same
as --color=always. [default: auto]
--no-color Disable colorization (alias of --color=never).
--progress / --no-progress Show progress indicators during long operations.
Disabled for non-interactive output (pipes, dumb
terminals, CI) and by --accessible. [default:
progress]
--theme [dark|dracula|light|manpage|monokai|nord|solarized_dark]
Color theme used for help screens. [default:
dark]
--show-params Show all CLI parameters, their provenance,
defaults and value, then exit.
--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
Rendering style of tables. [default: rounded-
outline]
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
-q, --quiet Decrease the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
--tree Show the tree of nested subcommands and exit.
--man Show the command's man page (roff) and exit.
--version Show the version and exit.
--columns [fruit,count] Restrict and reorder table columns, SQL SELECT-
style. Comma-separated list of column IDs.
Default: all columns in canonical order.
--sort-by [fruit|count] Sort table by this column. Repeat to set
priority. [default: fruit]
-h, --help Show this message and exit.
$ stock --table-format rounded-outline --sort-by fruit
╭────────┬───────╮
│ Fruit │ Count │
├────────┼───────┤
│ Apple │ 120 │
│ Banana │ 80 │
│ Cherry │ 50 │
╰────────┴───────╯
For programmatic use without a CLI option, render_table() accepts a sort_key callable:
from click_extra import command, echo
from click_extra.table import render_table, TableFormat
@command
def sorted_demo():
"""Render a table sorted alphabetically."""
data = [["Cherry", "50"], ["Apple", "120"], ["Banana", "80"]]
output = render_table(
data,
headers=["Fruit", "Count"],
table_format=TableFormat.ROUNDED_OUTLINE,
sort_key=lambda row: row[0],
)
echo(output)
$ sorted-demo
╭────────┬───────╮
│ Fruit │ Count │
├────────┼───────┤
│ Apple │ 120 │
│ Banana │ 80 │
│ Cherry │ 50 │
╰────────┴───────╯
click_extra.table API¶
classDiagram
Enum <|-- TableFormat
ExtraOption <|-- ColumnsOption
ExtraOption <|-- SortByOption
ExtraOption <|-- TableFormatOption
MultiChoice <|-- ColumnsType
Collection of table rendering utilities.
- class click_extra.table.TableFormat(*values)[source]
Bases:
EnumEnumeration of supported table formats.
Hard-coded to be in alphabetical order. Content of this enum is checked in unit tests.
Warning
The
youtrackformat is missing in action from any official JetBrains documentation. It will be removed in python-tabulate v0.11.- ALIGNED = 'aligned'
- ASCIIDOC = 'asciidoc'
- COLON_GRID = 'colon-grid'
- CSV = 'csv'
- CSV_EXCEL = 'csv-excel'
- CSV_EXCEL_TAB = 'csv-excel-tab'
- CSV_UNIX = 'csv-unix'
- DOUBLE_GRID = 'double-grid'
- DOUBLE_OUTLINE = 'double-outline'
- FANCY_GRID = 'fancy-grid'
- FANCY_OUTLINE = 'fancy-outline'
- GITHUB = 'github'
- GRID = 'grid'
- HEAVY_GRID = 'heavy-grid'
- HEAVY_OUTLINE = 'heavy-outline'
- HJSON = 'hjson'
- HTML = 'html'
- JIRA = 'jira'
- JSON = 'json'
- JSON5 = 'json5'
- JSONC = 'jsonc'
- LATEX = 'latex'
- LATEX_BOOKTABS = 'latex-booktabs'
- LATEX_LONGTABLE = 'latex-longtable'
- LATEX_RAW = 'latex-raw'
- MEDIAWIKI = 'mediawiki'
- MIXED_GRID = 'mixed-grid'
- MIXED_OUTLINE = 'mixed-outline'
- MOINMOIN = 'moinmoin'
- ORGTBL = 'orgtbl'
- OUTLINE = 'outline'
- PIPE = 'pipe'
- PLAIN = 'plain'
- PRESTO = 'presto'
- PRETTY = 'pretty'
- PSQL = 'psql'
- ROUNDED_GRID = 'rounded-grid'
- ROUNDED_OUTLINE = 'rounded-outline'
- RST = 'rst'
- SIMPLE = 'simple'
- SIMPLE_GRID = 'simple-grid'
- SIMPLE_OUTLINE = 'simple-outline'
- TEXTILE = 'textile'
- TOML = 'toml'
- TSV = 'tsv'
- UNSAFEHTML = 'unsafehtml'
- VERTICAL = 'vertical'
- XML = 'xml'
- YAML = 'yaml'
- YOUTRACK = 'youtrack'
- property is_markup: bool
Whether this format is a markup rendering.
ANSI codes never reach a markup rendering raw: they are either translated to the format’s native styling (see
supports_styling) or stripped from cell values. Forcing--coloron the command line preserves them as-is in the markup formats without styling support.
- property supports_styling: bool
Whether ANSI codes are translated to this format’s native styling.
See
STYLED_FORMATSfor the registry, and the rationale behind each excluded markup format.
- click_extra.table.MARKUP_FORMATS = frozenset({TableFormat.ASCIIDOC, TableFormat.CSV, TableFormat.CSV_EXCEL, TableFormat.CSV_EXCEL_TAB, TableFormat.CSV_UNIX, TableFormat.GITHUB, TableFormat.HJSON, TableFormat.HTML, TableFormat.JIRA, TableFormat.JSON, TableFormat.JSON5, TableFormat.JSONC, TableFormat.LATEX, TableFormat.LATEX_BOOKTABS, TableFormat.LATEX_LONGTABLE, TableFormat.LATEX_RAW, TableFormat.MEDIAWIKI, TableFormat.MOINMOIN, TableFormat.ORGTBL, TableFormat.PIPE, TableFormat.RST, TableFormat.TEXTILE, TableFormat.TOML, TableFormat.TSV, TableFormat.UNSAFEHTML, TableFormat.XML, TableFormat.YAML, TableFormat.YOUTRACK})
Subset of table formats that are considered as markup rendering.
- click_extra.table.STYLED_FORMATS: dict[TableFormat, Callable[[str], str]] = {TableFormat.HTML: <function ansi_to_html>, TableFormat.JIRA: <function ansi_to_jira>, TableFormat.LATEX: <function ansi_to_latex>, TableFormat.LATEX_BOOKTABS: <function ansi_to_latex>, TableFormat.LATEX_LONGTABLE: <function ansi_to_latex>, TableFormat.LATEX_RAW: <function ansi_to_latex>, TableFormat.MEDIAWIKI: <function ansi_to_html>, TableFormat.TEXTILE: <function ansi_to_textile>, TableFormat.UNSAFEHTML: <function ansi_to_html>}
Markup formats able to express styles natively, mapped to their ANSI translator.
print_table()runs the rendered output of these formats through their translator, converting the ANSI codes carried by cells and headers into the format’s own styling markup: inline-CSS HTML<span>s for the HTML pair and MediaWiki (which accepts embedded HTML), Textile%{...}spans, Jira{color:...}macros, and xcolor-based LaTeX macros.Note
Translation happens on the rendered output, not on cell values, on purpose. tabulate escapes cell content for some formats (
htmlescapes HTML entities, non-rawlatexvariants escape TeX specials) while ANSI sequences pass through unscathed, so pre-render translation would get its markup mangled by those escaping rules. Post-render injection also keeps column-width computation on the ANSI text, which tabulate measures correctly.Important
Every markup format absent from this registry keeps the historical behavior: ANSI codes are stripped from cells before rendering. The verdict, format by format:
asciidoc: no portable inline styling. Colors require stylesheet-defined roles or+++passthrough blocks tied to the HTML backend, both lossy and non-standard.csv,csv-excel,csv-excel-tab,csv-unix,tsv: data interchange formats, with no concept of styling.github,pipe: GitHub sanitizes inlinestyleattributes from rendered Markdown, so translated HTML spans would not display any color there. Raw ANSI can still be forced with--colorfor terminal Markdown viewers which support escape sequences.hjson,json,json5,jsonc,toml,xml,yaml: structured serialization formats meant for programmatic consumption. Styling is presentation, not data.moinmoin: MoinMoin wiki markup has no standard inline color syntax, and embedded HTML is disabled by default.orgtbl: Org-mode has emphasis markers but no inline color markup.rst: reStructuredText needs custom roles backed by a stylesheet for inline color; there is no standard inline syntax.youtrack: undocumented by JetBrains and scheduled for removal in python-tabulate 0.11.
- click_extra.table.DEFAULT_FORMAT = TableFormat.ROUNDED_OUTLINE
Default table format, if none is specified.
- click_extra.table.RECORD_KEY = 'record'
Key used for each record in structured formats that require named containers (TOML
[[record]], XML<record>).
- click_extra.table.XML_ROOT_KEY = 'records'
Root element name for XML table output.
- click_extra.table.SERIALIZATION_FORMATS = frozenset({TableFormat.HJSON, TableFormat.JSON, TableFormat.JSON5, TableFormat.JSONC, TableFormat.TOML, TableFormat.XML, TableFormat.YAML})
Structured serialization formats whose renderers escape raw ESC bytes, making post-render
strip_ansi()ineffective.
- click_extra.table.render_table(table_data, headers=None, table_format=None, sort_key=None, **kwargs)[source]
Render a table and return it as a string.
- click_extra.table.print_table(table_data, headers=None, table_format=None, sort_key=None, **kwargs)[source]
Render a table and print it to the console.
ANSI codes carried by cell values and headers depend on the format:
Markup formats with native styling support (see
STYLED_FORMATS) get them translated to the format’s own styling markup, unless color output is disabled (--no-color,NO_COLOR, …).Other markup formats get them stripped from cell values before rendering, unless
--coloris explicitly forced on the command line.Plain-text formats keep them raw, and defer to
echo()’s sensitivity to the global colorization settings.
- click_extra.table.serialize_data(data, table_format, *, default=None, root_element='records', **kwargs)[source]
Serialize arbitrary Python data to a structured format.
Unlike
render_table()which expects tabular rows and headers, this function accepts any JSON-compatible data structure (dicts, lists, nested combinations) and serializes it to the requested format.Only formats in
SERIALIZATION_FORMATSare supported.- Parameters:
data (
Any) – Arbitrary data to serialize (dicts, lists, scalars).table_format (
TableFormat) – Target serialization format.default (
Callable|None) – Fallback serializer for types not natively supported. Defaults tostr, soPathand similar types are stringified automatically. Set to a custom callable for different behavior.root_element (
str) – Root element name for XML output.kwargs – Extra keyword arguments forwarded to the underlying serializer (like
sort_keysorindentfor JSON).
- Raises:
ValueError – If the format is not a serialization format.
- Return type:
- click_extra.table.print_data(data, table_format, *, default=None, root_element='records', package='click-extra', **kwargs)[source]
Serialize arbitrary Python data and print it to the console.
Wraps
serialize_data()with user-friendly error handling for missing optional dependencies.- Parameters:
data (
Any) – Arbitrary data to serialize.table_format (
TableFormat) – Target serialization format.default (
Callable|None) – Fallback serializer for custom types. Defaults tostr.root_element (
str) – Root element name for XML output.package (
str) – Package name for install instructions in error messages.kwargs – Extra keyword arguments forwarded to the underlying serializer.
- Return type:
- class click_extra.table.TableFormatOption(param_decls=None, type=EnumChoice('aligned', 'asciidoc', 'colon-grid', 'csv', 'csv-excel', 'csv-excel-tab', 'csv-unix', 'double-grid', 'double-outline', 'fancy-grid', 'fancy-outline', 'github', 'grid', 'heavy-grid', 'heavy-outline', 'hjson', 'html', 'jira', 'json', 'json5', 'jsonc', 'latex', 'latex-booktabs', 'latex-longtable', 'latex-raw', 'mediawiki', 'mixed-grid', 'mixed-outline', 'moinmoin', 'orgtbl', 'outline', 'pipe', 'plain', 'presto', 'pretty', 'psql', 'rounded-grid', 'rounded-outline', 'rst', 'simple', 'simple-grid', 'simple-outline', 'textile', 'toml', 'tsv', 'unsafehtml', 'vertical', 'xml', 'yaml', 'youtrack'), default=TableFormat.ROUNDED_OUTLINE, expose_value=False, is_eager=True, help='Rendering style of tables.', **kwargs)[source]
Bases:
ExtraOptionA pre-configured option that is adding a
--table-formatflag to select the rendering style of a table.The selected table format ID is made available in the context in
ctx.meta[click_extra.context.TABLE_FORMAT], and two helper methods are added to the context:ctx.render_table(table_data, headers, **kwargs): renders and returns the table as a string,ctx.print_table(table_data, headers, **kwargs): renders and prints the table to the console.
Where:
table_datais a 2-dimensional iterable of iterables for rows and cells values,headersis a list of string to be used as column headers,**kwargsare any extra keyword arguments supported by the underlying table formatting function.
- class click_extra.table.ColumnSpec(id, label, description='')[source]
Bases:
objectRich description of a single column in a rendered table.
Three fields, all required-by-convention even though
descriptiondefaults to empty so quick prototypes do not have to write a sentence for every column:id: stable, snake_case identifier used by--columnsto address the column, to key structured-format serializations, and to thread state throughclick_extra.context.COLUMNS.label: the human-readable header shown at the top of the rendered table.description: a MyST/Markdown blurb describing what the column represents. Used to auto-generate the column reference in the documentation.
Note
Frozen + slots: instances are immutable and lightweight. Tuples of
ColumnSpecare intended to be defined as module-level constants (likeclick_extra.parameters.ShowParamsOption.TABLE_HEADERS).- id: str
Stable, snake_case identifier addressing this column from CLI flags and code.
- label: str
Human-readable header label rendered at the top of the table.
- description: str
MyST/Markdown description of what the column carries.
Used to auto-generate the Available columns section in the docs via the
show_params_columns_tableMyST substitution. Plain text without inline markup is fine: links and emphasis are optional sugar.
- click_extra.table.render_columns_markdown_table(columns)[source]
Render an iterable of
ColumnSpecas a 2-column Markdown table.Output shape:
| Column | Description | | :--- | :--- | | `Label` | description | ...
Suitable for inlining into MyST documents via
myst_substitutionsso the Available columns reference can be auto-generated from a single source of truth.- Return type:
- click_extra.table.select_columns(columns, selected_ids)[source]
Filter and reorder
columnsaccording toselected_ids.Returns
columnsunchanged whenselected_idsis falsy (no projection). Otherwise yields the matchingColumnSpecin the orderselected_idsspecifies, SQL-SELECT-style. RaisesKeyErrorfor unknown IDs so the caller can convert it into aclick.UsageError.- Return type:
- click_extra.table.select_row(row, selected_ids, canonical_ids)[source]
Build a positional row by reading cells from
rowin the selection order.Falls back to
canonical_idswhenselected_idsis empty / unset, so the row preserves its canonical column order in the absence of any user selection.- Return type:
- class click_extra.table.ColumnsType(accepted_ids=())[source]
Bases:
MultiChoiceColumn-flavored alias of
click_extra.types.MultiChoice.Pins the comma separator and case-sensitive matching (column IDs are snake_case identifiers, not free-form strings), and renames the metavar fallback to
COLUMNSinstead of the genericMULTI. Theaccepted_idsconstructor keyword is a column-flavored alias ofMultiChoice.choices.Initialize the type.
- Parameters:
choices – the accepted values. When non-empty,
convert()rejects unknown tokens withfail. When empty, the type behaves as a pure separator-aware parser and leaves validation to the consumer.separator – the token boundary. Use any single character; this also drives the metavar rendering (
[a<sep>b<sep>c]).case_sensitive – when
False, tokens matchchoicescase-insensitively and the returned tuple holds the canonical (original-case) values fromchoices.
- name: str = 'columns'
the descriptive name of this type
- class click_extra.table.ColumnsOption(param_decls=None, columns=None, type=None, default=(), expose_value=False, is_eager=True, help='Restrict and reorder table columns, SQL SELECT-style. Comma-separated list of column IDs. Default: all columns in canonical order.', **kwargs)[source]
Bases:
ExtraOptionA
--columnsoption that lets users restrict and reorder table columns.Accepts a comma-separated list of column IDs, SQL-
SELECT-style:$ my-cli --columns id,spec,value --show-params
The selection is stored in
ctx.meta[click_extra.context.COLUMNS]and consumed by table-rendering callbacks (likeclick_extra.parameters.ShowParamsOption) to project rows + headers before rendering.Pass
columns=at construction time with the column registry the option should advertise: the help text then lists the accepted IDs and the default selection, and the callback validates the user input against that registry so unknown IDs fail fast with aclick.UsageError. Withoutcolumns=, the option stays generic: it parses any IDs and leaves validation to the downstream consumer.Empty / unset means render every column in canonical order: the default behavior, indistinguishable from not passing
--columnsat all.- columns: tuple[ColumnSpec, ...]
Column registry this option advertises and validates against (may be empty).
- class click_extra.table.SortByOption(*header_defs, param_decls=None, columns=None, default=None, expose_value=False, cell_key=None, help='Sort table by this column. Repeat to set priority.', **kwargs)[source]
Bases:
ExtraOptionA
--sort-byoption whose choices are derived from column definitions.Stores the selected column IDs in
ctx.meta[click_extra.context.SORT_BY]and bakes a row sort intoctx.print_tableso that table output is automatically sorted, without changing its(table_data, headers)call contract. The option acceptsmultiple=True, so users can repeat--sort-byto define a multi-column sort priority.Column definitions may be
ColumnSpecinstances or raw(label, column_id)tuples, passed positionally or via thecolumns=keyword. Passing aColumnSpecregistry viacolumns=lets the same tuple drive bothColumnsOption(--columns) and--sort-by, so the two options stay in sync from a single source of truth.COLUMNS = ( ColumnSpec("package_id", "Package ID"), ColumnSpec("package_name", "Name"), ColumnSpec("manager_id", "Manager"), ) @command @table_format_option @columns_option(columns=COLUMNS) @sort_by_option(columns=COLUMNS) @pass_context def my_cmd(ctx): ctx.print_table(rows, [col.label for col in COLUMNS])
- init_sort(ctx, param, sort_columns)[source]
Bake the row sort key into
ctx.print_table.Builds the sort key from this option’s column definitions and the selected
sort_columns, then rebindsctx.print_tabletoprint_table()with that key applied. The call contract is the same sorted or not:ctx.print_table(table_data, headers).- Return type: