Library / SDK
turicas/rows avatar
turicas/rows

rows gives one Python interface to every tabular file format

A common, beautiful interface to tabular data, no matter the format

886 stars138 forksPythonLGPL-3.0

At a glance

What is it?
rows is a Python library that imports tabular data whatever the format, detects the column types and hands back high level Python objects, with a plugin architecture where each format is an optional extra. The last tagged release is 0.4.1 from 2019, yet the default branch is develop and the repository is still being pushed, so the published version and the working code have drifted apart.
Who is it for?
Adopt rows if you handle files whose format you do not control, spreadsheets and PDFs that arrive from other people, and you want one code path instead of a branch per file type, and if you are willing to install the extras for the formats you need.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 10 days 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The pitch is that you stop writing parsers

The project description is a common, beautiful interface to tabular data, no matter the format, and the README puts the promise directly. No matter in which format your tabular data is, rows will import it, automatically detect types and give you high-level Python objects so you can start working with the data instead of trying to parse it. It is also locale and unicode aware.

That framing is worth pausing on, because it is a claim about a category of work rather than about one format. Every data pipeline eventually contains a block of code that opens a file whose format depends on who sent it, and that block is a different parser for every case. Spreadsheets with merged cells, CSVs with a currency symbol in one column and a number in the next, PDFs that are really tables, ODS files from a spreadsheet suite nobody standardised on. Each of those has an edge case that breaks the naive reader.

The audience is a Python developer whose input format is not under their control. A data journalist with files from sources, an analyst processing a directory of exports, a researcher collecting datasets, a developer building an import feature for a product where the user chooses the file.

The licence is LGPL-3.0, the primary language is Python, the default branch is develop, and the last push to the repository was on 2026-10-13. The project is on PyPI, with a badge for the current version and one for monthly downloads, and the documentation is at turicas.info/rows.

Every format is an extra, and the base install is almost nothing

The setup.py is where the design becomes clear, because the dependency list for the base package is one line long. The requirements file for the core contains only requests, and the extras are declared as a dictionary.

The keys of that dictionary are the formats and the interfaces. There is csv, which needs nothing at all. There is detect, which needs file-magic on Python 3 and above and charset-normalizer on Python 3.7 and above, and that is the format sniffing, the part that decides what a file is before it is read. There is html and ods, which both need lxml, and parquet, which needs the parquet package, and xls with xlrd and xlwt, and xlsx with a defusedxml based stack. There is postgresql with psycopg2-binary, which is what lets the same table object be a database query rather than a file. There is utils with requests, requests-cache and tqdm, and the cli extra with click, plus a cli-extra that combines both.

PDF is the interesting case, because it has two implementations. The pdf extra uses pdfminer.six, with pinned versions selected by Python version, and there is a separate pdf-pymupdf extra that uses pymupdf with cached-property. The README's project topics list includes pdf, and the two extras are a good example of why the plugin structure pays off: a format with two viable back ends becomes two extras rather than a choice baked into the core.

So the shape of the library is a small core with a plugin per format, and the plugin is an optional dependency group. The consequence for a user is that pip install rows gives you a library that can read CSV and nothing else, and every other format costs one more line in your requirements file.

Installing and contributing, both in three commands

Installation is the shortest part of the README:

bash
pip install rows

The README then points to the Installation section of the documentation for the other ways to install it.

The contribution guide is more interesting, because it tells you how the plugin architecture is worked on. The preferred way to start contributing is to create a virtual environment, with virtualenv, virtualenvwrapper, pyenv or whatever tool you prefer, and the commands are:

bash
mkvirtualenv rows

then installing all the plugins' dependencies with the all extra:

bash
pip install --editable .[all]

and then the development dependencies:

bash
pip install -r requirements-development.txt

The all extra is the answer to a question a new contributor will have, which is how do I get every format installed so I can test my plugin against them. The editable install means the checkout is the importable package, so a change to a plugin takes effect without reinstalling.

The documentation has the same treatment. The README says you can read the documentation online, or build and browse it locally by running the make docs-serve target after installing requirements-development.txt. The repository has a mkdocs.yml, a docs/ directory and a Makefile for that target, so the documentation is a static site built from the same tree.

The Docker development stack runs a Postgres service and one image per Python

The compose.yml is a development environment rather than a distribution channel, and its structure tells you how the project is tested.

There is one service per Python version, each with its own profile, so that a developer starts only the one they need. The py313 service is the base one, and the others extend it and override the Python version argument, giving py312, py311, py310, py39 and py38 services. A pydebian profile builds from debian.Dockerfile instead of the slim Python image, which is how the project tests against a different base.

Each service mounts the working tree at /app and a data directory at /data, runs as the host user through a user setting, and depends on a postgres service with a health check condition, because the project tests its database backend as well as its file readers.

The Dockerfile shows the same care about the runtime environment. It takes the Python image and Debian version as build arguments, sets the unbuffered environment variable for logs, creates a non root user with a configurable uid and gid, and then configures the locale, writing en_US.UTF-8 and pt_BR.UTF-8 into the locale generation file with the comment that this is required to run tests. That last detail is the kind of thing that explains the README's claim that the library is locale and unicode aware: the test suite actually runs under a non default locale, so the behaviour is exercised rather than assumed.

The system packages installed are the ones the plugins need, libmagic1 for the format detection, libmupdf-dev for the PDF plugin, libpq-dev for PostgreSQL, libxml2 and libxslt for the XML based formats, plus the client libraries for Postgres and SQLite. The pip installs use a build cache mount, which is the standard way to keep a Docker build from re-downloading large wheels on every change.

A CLI and a library, with examples for both

The package offers two ways in, and the extras list makes the split explicit.

The cli extra adds click, which is the command line framework, and the cli-extra combines click with the utils set, which is requests, requests-cache and tqdm. The utils extra on its own gives you the parts that reach out to the network with caching and a progress bar, which is what the detection and download helpers use. The postgresql extra adds psycopg2-binary, which is what lets a table come from a database query rather than a file.

The repository has an examples/ directory with three subdirectories, cli, data and library, which is the clearest evidence of the two front ends and of the sample files they work on.

The point of the plugin design is that the interface above the plugins does not change. A table read from a CSV, from an XLSX file, from a PDF and from a Postgres query is the same object with the same access pattern, and the type detection happens inside the plugin, where the format specific knowledge belongs. The alternatives to this approach are either a branch in your code for each format, or a conversion step that produces a single intermediate format and loses information on the way, such as a spreadsheet with a date column that arrives as text and is never parsed.

For an evaluator, the question to ask is how a new format is added, and the answer is visible in the extras dictionary: a new key, a dependency list, and whatever the reader implementation needs. The README does not document that interface in detail, and the documentation site is where you would look.

The published version and the working code have drifted apart

This is the part of the repository an evaluator has to look at carefully.

The recent releases are v0.4.1, labelled as a bugfix release, on 2019-02-14, v0.4.0 on 2019-02-13 and 0.3.1 on 2017-05-09. The last tagged release is therefore more than seven years old, and the version on PyPI is what the README badge reports.

The repository, meanwhile, is not archived, the last push was on 2026-10-13, and the default branch is develop rather than master. The copyright line in setup.py runs from 2014 to 2025, and the file still has from __future__ import unicode_literals in it, which is a Python 2 compatibility artefact that has not been removed.

Put those two facts together and the practical reading is that the work continues on the develop branch without new tags being cut, so the published package and the head of the repository are different things. That is a normal pattern for a project whose maintainer is the main contributor, and it is a pattern that works well if you vendor or install from the branch, and badly if you depend on a stable release for a production system, because a library that does not cut releases cannot promise that a version you depend on will keep behaving the same way.

The dependency pins are the other thing to check. The extras still contain Python 2.7 and 3.5 era pins, such as a pdfminer.six version selected for Python 2.7, and the classifiers in the project table, which the README badge links to as supported Python versions, are the authoritative list of what the current code supports.

The rest of the repository is a mature working setup: a Dockerfile and a debian.Dockerfile, a compose.yml, a Makefile, a lint.sh and a smoke-tests.sh, a pytest.ini, an AUTHORS.md, examples/ with cli, data and library directories, and a to-do/ directory that says more about the project's direction than any badge does.

Editorial conclusion

Adopt rows if you handle files whose format you do not control, spreadsheets and PDFs that arrive from other people, and you want one code path instead of a branch per file type, and if you are willing to install the extras for the formats you need. Do not adopt it for a project with a single known format, where pandas or the standard library csv module is a smaller dependency, and do not pin to 0.4.1 expecting current behaviour, because that release is from 2019 while the work continues on the develop branch. Before you install, check the extras list for the formats you actually need, since the base install pulls almost nothing, and read the documentation at turicas.info/rows for the plugin interface, because a new format is a new plugin rather than a flag.

Frequently asked questions

What does the rows Python library do?

It imports tabular data in whatever format it arrives in, automatically detects the column types, and returns high level Python objects so you can work with the data instead of parsing it. It is also locale and unicode aware.

How do I install rows?

With pip install rows. Each format is an optional extra, so the base install only covers CSV, and the setup.py extras list includes detect, html, ods, parquet, pdf, pdf-pymupdf, xls, xlsx, postgresql, utils and cli.

How do I set up a development environment for rows?

Create a virtual environment, for example with mkvirtualenv rows, then run pip install --editable .[all] to install every plugin's dependencies, and pip install -r requirements-development.txt for the development dependencies. The documentation can be browsed locally with make docs-serve.

How does rows handle PDF files?

There are two PDF extras. The pdf extra uses pdfminer.six with version pins selected by Python version, and the pdf-pymupdf extra uses pymupdf with cached-property, so a format with two viable back ends becomes two extras.

Is the rows package still maintained?

The repository is not archived and the last push was on 2026-10-13, with the default branch set to develop, but the last tagged release is v0.4.1 from 2019-02-14. The licence is LGPL-3.0 and the copyright line in setup.py runs from 2014 to 2025.

Official sources

  1. Issues
  2. License: LGPL-3.0
  3. README
  4. Releases
  5. turicas/rows on GitHub
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/turicas-rows.svg)](https://hysenlabs.com/projects/turicas-rows)

Was this useful?