CLI tool
astanin/python-tabulate avatar
astanin/python-tabulate

python-tabulate keeps its version out of the repository and its formats in a list of thirty five

Pretty-print tabular data in Python, a library and a command-line utility. Repository migrated from bitbucket.org/astanin/python-tabulate.

2,587 stars219 forksPythonMIT

At a glance

What is it?
A one function table printer with a matching command line utility, thirty five output formats, no runtime dependencies, and a version number that is generated from git metadata at build time rather than stored in the project files.
Who is it for?
python-tabulate fits a script that needs a readable table in a terminal, in a log line, or in markup for another system, and the one function API keeps the surface small. Check three things before depending on it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The version number is generated at build time and never stored here

There is no version string anywhere in the project files. The packaging metadata marks the version as dynamic and the build backend is `flit_scm:buildapi`, with a setuptools_scm section that writes the resolved value into `tabulate/_version.py` at build time. That generated file is the one path the lint configuration excludes, which confirms it is produced rather than authored. The consequence for anybody reading the repository is that the version cannot be recovered from a checkout: it exists only in the built artifact, and it is derived from whatever git metadata the build saw. The repository publishes no GitHub releases either, so there is no tag list on the project page to compare against. What remains as a record is a plain `CHANGELOG` file with no extension at the root and a `HOWTOPUBLISH` file beside it, which is where the tag driven release process is written down.

Three install shapes, and one of them is decided by an environment variable

The install section offers three different shapes rather than one command with flags.

shell
pip install tabulate

That installs the library and a command line utility named `tabulate`, placed in `bin` on Linux or in `Scripts` on Windows. A user scoped install puts the same executable under `~/.local/bin` on Linux and under `%APPDATA%\Python\Scripts` on Windows. The third shape is the interesting one, because the choice is made by an environment variable read during installation rather than by a flag: on Unix-like systems `TABULATE_INSTALL=lib-only` before the same pip command, and on Windows the same variable set with `set` on its own line. The entry point it controls is declared in the metadata as `tabulate = "tabulate.cli:_main"`, so the utility and the library come from one distribution and one is optional at install time.

The install docs still point at Python 3.9 directories the package no longer supports

The install section illustrates where the executable lands with a Windows example path under `C:\Python39\Scripts\tabulate.exe`, and another sentence states only that tabulate is a Python 3 library. The packaging metadata is narrower than both. It requires Python 3.10 or newer and lists classifiers for 3.10, 3.11, 3.12, 3.13, and 3.14. So the documentation's worked example describes an interpreter series that the current metadata excludes, and the one sentence about Python versions predates the floor being raised. It is a documentation drift rather than a broken promise, since pip will refuse the install on an older interpreter anyway, but it is the kind of drift that matters for a package this widely used, and it shows up in the first screen of the document rather than in a corner of it.

Thirty five output formats, including two HTML variants

The format argument accepts thirty five named values. Counting them out: plain, simple, github, five grid variants, seven outline variants, pipe, orgtbl, asciidoc, jira, presto, pretty, psql, rst, mediawiki, moinmoin, html, unsafehtml, four latex variants, textile, and tsv. Most of them exist to hand output to another system rather than to a terminal, which makes the library a converter as much as a formatter. Two details in that list carry weight. The default is simple, and the document says plainly that the default may change in a future version. And there are two HTML formats rather than one, the second named unsafehtml, with the visible part of the document not spelling out what the second one skips. The github format is defined as the pipe format with the same alignment colons, so it is a preset rather than a separate renderer, and several of the others target wiki pages and issue trackers, which means the same table can be pasted into a ticket or a wiki page without hand editing it first.

Seven kinds of input and not one required dependency

The documented input types number seven: an iterable of iterables, an iterable of dictionaries, a dictionary of iterables, a list of dataclasses, a two-dimensional NumPy array, NumPy record arrays, and a pandas DataFrame. The metadata declares no runtime dependencies at all, and the only optional extra is `widechars`, which pulls `wcwidth` at version 0.6.0 or newer for character width handling. Reading those two facts together explains the design: NumPy and pandas objects are accepted by duck typing rather than by importing either library, so a script that formats a list of lists never pays for them and a script that formats a DataFrame never has to declare pandas. It also means an object that merely looks like a DataFrame is accepted, and the failure, if there is one, surfaces as an attribute error rather than as a type check. Two more arguments sit alongside the data. Headers can be a plain list, the string firstrow to lift the first row of data, the string keys to take them from a dictionary or DataFrame, or a dictionary that renames keys in place of the default labels. And a showindex argument controls the row index column, which by default appears only for DataFrame input and otherwise takes always, never, or any iterable of your own identifiers.

The README's own examples are executed as the test suite

The pytest configuration runs doctests over the package modules and over this README. The addopts line adds `--doctest-modules`, which collects docstring examples inside `tabulate/`, and `--doctest-glob=README.md`, which collects the `pycon` blocks in the document itself, so every printed table in the README is checked against the code that produces it and a formatting change breaks the build. The same line carries `--ignore=benchmark`, so the `benchmark/` directory is excluded from collection entirely and its code is never imported by the suite. Two consequences follow. The documentation cannot drift from the output, which is unusual and valuable. And the test run says nothing about the benchmark harness, since the one part of the tree that measures anything is the part the tests skip.

The lint configuration exempts complexity and zip strictness on purpose

The ruff settings describe the shape of the code as much as the style. Line length is 99, and the selected rule families add warnings, bugbear checks, comprehensions, string consistency, import sorting, complexity, and pyupgrade. Three rules are then switched off: the bugbear check for `zip` without an explicit `strict` argument, the comparison check for type equality, and the complexity check itself, with the mccabe limit separately set to 22. A complexity limit of 22 is far above the usual default of ten, and the explicit exemption says the project has decided not to enforce the check at all. The import sorting configuration also names a folder called `common` as first party, which the root listing does not contain, so that package directory must live inside `tabulate/` itself.

A Beta classifier, a master branch, and a changelog without an extension

Three small signals describe the project's state. The classifier says Development Status 4, Beta. The default branch is `master` rather than `main`, which dates the repository's conventions to a period before that rename became the norm. The change history is a file called `CHANGELOG` with no extension, included explicitly in the source distribution alongside the `test/` directory and `tox.ini`. The last commit on that branch is dated 2026-03-11 and the project is not archived. Two `homepage` values also disagree in a small way: the recorded homepage for the repository points at the PyPI project page, while the URL table inside the packaging metadata points at the repository itself, which is the usual split but worth knowing when you are deciding which link belongs in your own documentation.

Editorial conclusion

python-tabulate fits a script that needs a readable table in a terminal, in a log line, or in markup for another system, and the one function API keeps the surface small. Check three things before depending on it. The version is written at build time from git tags, so if you need a repeatable version, build your own artifact rather than installing from a checkout. The default table format is simple and the README says that default may change in a future version, so pin tablefmt if the exact characters matter. And the last commit on the default branch is dated 2026-03-11, with no GitHub releases to fall back on.

Frequently asked questions

what is python tabulate

It is a Python library and command line utility for pretty printing tabular data. The library exposes a single function, `tabulate`, which takes a list of lists or another tabular data type and returns a formatted plain text table. The same distribution installs a `tabulate` command for the terminal.

how to install python tabulate

`pip install tabulate` installs the library and the command line utility together. `pip install tabulate --user` limits the install to the current user, and setting `TABULATE_INSTALL=lib-only` before the pip command installs the library only on Unix-like systems. The package requires Python 3.10 or newer.

How many table formats does tabulate support?

Thirty five named values, including plain, simple, github, five grid variants, seven outline variants, pipe, orgtbl, asciidoc, jira, presto, pretty, psql, rst, mediawiki, moinmoin, html, unsafehtml, four latex variants, textile, and tsv. The default is simple, and the README states that the default may change in future versions.

Does tabulate depend on pandas or numpy?

The packaging metadata declares no runtime dependencies at all, even though the documented input types include two-dimensional NumPy arrays, NumPy record arrays, and pandas DataFrame. The only optional extra is `widechars`, which pulls wcwidth 0.6.0 or newer.

Does python-tabulate publish GitHub releases?

No. The repository has no GitHub releases, and the package version is generated at build time from git metadata by flit_scm, with setuptools_scm writing it into tabulate/_version.py. The change history is kept in a CHANGELOG file at the repository root.

Official sources

  1. astanin/python-tabulate on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/astanin-python-tabulate.svg)](https://hysenlabs.com/projects/astanin-python-tabulate)