odsslicer
Python reader and writer for .ods files (OpenDocument Spreadsheet — LibreOffice /
OpenOffice Calc), with a numpy-inspired indexing API:
from odsslicer import ODSReader
table = ODSReader("workbook.ods")
sheet = table.sheet("Sheet1")
sheet["A1"].value # typed value: str / float / bool / date / time / None
sheet["A1:B3"].to_numpy() # any block as a numpy array
sheet[:, 0] # entire column A
sheet["C1"].formula = "SUM(A1:A10)"
sheet["C1"].style.bold = True
table.save("out.ods") # add recalculate=True to have LibreOffice compute formulas/pivots
odsslicer works directly on the ODF XML (via BeautifulSoup/lxml), so it preserves what
other tools tend to drop — cell formats (currency, percentage, date, time), formulas, merged
and repeated cells, styles, comments — and writes them back faithfully. It has no calculation
engine of its own: like ODF itself, it describes what to compute — and can hand the file to a
local LibreOffice (save(..., recalculate=True)) to compute formulas and pivot tables for you.
Full documentation with an example for every feature: DOCS.md.
Installation
pip install odsslicer
Requires Python ≥ 3.10. Dependencies (beautifulsoup4, lxml, numpy) are installed
automatically. For local development:
git clone https://github.com/antnardo/odsslicer.git
cd odsslicer
pip install -e ".[test]"
What it does
Each line links to the detailed section (with examples) in DOCS.md.
| Area | Feature | Example |
|---|---|---|
| Reading | numpy-style indexing — addresses, slices, out-of-range returns empty cells | sheet["A1"], sheet[0, 0], sheet["A1:B3"], sheet[:, 0] |
| Typed cells — value, displayed text, format, address | cell.value, cell.text, cell.format |
|
Arrays — to_list(), to_numpy(), numpy-like shapes |
sheet["A1:B3"].to_numpy() |
|
| Writing | Values of every ODF type, then save() |
sheet["A1"].value = 42.5 |
| Ranges at once — broadcast or element-wise | sheet["A1:C1"].value = [1, 2, 3] |
|
| Auto-unrolling of repeated/merged cells on first write | transparent | |
| Auto-growth when writing past the sheet's extent | sheet["Z100"].value = 1 |
|
| Displayed text inferred from the document's own formats | "50,00 %", "05/01/30" |
|
| Files & sheets | New file from scratch | ODSReader.new() |
| Add / rename / reorder / delete sheets — renaming fixes cross-sheet formulas | table.rename_sheet("Sheet1", "Q4") |
|
| Structure | Insert rows/columns — formula references and merges follow | sheet.insert_rows(2, 5) |
| Delete rows/columns — formula references follow, batchable | sheet.delete_rows([3, 7, 20]) |
|
| Copy cells/ranges — value + formula + style, overlap-safe | sheet.copy("A1:B2", "D5") |
|
Sort a range — stable, None last, formulas follow their row |
sheet.sort("A2:C10", by=1) |
|
| Merge / unmerge + read merge state | sheet.merge("A1:C2"), cell.merge_range |
|
| Formulas | Write in ordinary syntax, translated to ODF's | cell.formula = "IF(A1>0,1,-1)" |
| Read back in ordinary syntax | cell.formula_friendly |
|
| Fill across a range like a fill handle | cell.fill_formula("B3:B10") |
|
{r}/{c} templates for per-cell patterns |
sheet["A2:A10"].formula = "$A{r-1}+1" |
|
| Pivot tables — definition written, computed by the spreadsheet | sheet.create_pivot_table(...) |
|
| Recalculate with LibreOffice — formulas + pivot refresh, headless, no UNO needed | table.save("out.ods", recalculate=True) |
|
| Styles | Read and write cell styles — font, colors, alignment, borders, rotation, wrap… | cell.style.bold = True |
| Copy a style in one shot | b.style = a.style |
|
| Number formats — read, assign, or create from scratch | NumberFormat.create(table, "currency", ...) |
|
| Conditional formats (e.g. negatives in red) | fmt.add_condition("value()<0", red) |
|
| Row / column / sheet styles — height, width, visibility, tab color | sheet.column_style(0).width = "5cm" |
|
| Annotations | Comments — text, author, date, visibility | cell.comment = "Check this" |
| Hyperlinks | cell.hyperlink = "https://…" |
|
| Document | Properties — title, author, keywords, typed custom properties | table.properties.title = "Q4" |
See Known limitations for what's deliberately out of scope (no calculation engine, no charts/images, no partial rich text…).
Are there already equivalent PyPI modules?
| Package | Read/Write | Latest release | Notes |
|---|---|---|---|
odfpy |
Low-level R/W | 1.4.1 — Jan 2020 | Dormant, but still the brick pandas uses internally |
odfdo |
Full R/W | 3.24 — Aug 2026 | Actively maintained modern fork of odfpy; generic DOM-like API for all ODF document types |
pyexcel-ods3 |
R/W | 0.6.1 — Jan 2022 | Inactive; values only, no styles/formulas |
ezodf |
R/W | 0.3.2 — Dec 2015 | Abandoned |
pandas (engine="odf") |
Read (via odfpy) | follows pandas | Convenient for data frames; loses formulas and fine-grained formats |
python-calamine |
Read-only (Rust), fast | 0.8 — Jul 2026 | The best option for fast pure reading |
pandas-ods-reader |
Read-only → DataFrame | 1.0.2 — May 2025 | Maintained, limited scope |
(Versions and dates as of August 2026.)
Where odsslicer sits: a spreadsheet-shaped API (sheet["A1:B3"], numpy arrays, fill
handles, copy/sort/merge) rather than a generic ODF DOM, with read and write of the
things data-oriented tools usually lose — formats, formulas (in ordinary syntax), styles,
merged cells, comments, pivot definitions — and every write verified against a real
LibreOffice. If you only need to read values fast, use python-calamine; if you need to
manipulate arbitrary ODF documents (text, presentations) at the XML level, use odfdo.
Tests
pip install -e ".[test]"
pytest
The main suite (tests/test_odsslicer.py) covers every feature above plus regression tests
for the bugs fixed along the way; it runs on every push/PR via
GitHub Actions across Python 3.10 to 3.13.
tests/test_wild_files.py confronts the API with real-world files written by other
generators — Excel 16, a 2012-era LibreOffice 3.5, recent LibreOffice on Linux and Windows,
a Google Sheets export (see tests/wild/README.md
for provenance) — reading, writing and round-tripping each one.
tests/test_libreoffice_consistency.py is an opt-in suite that hands files odsslicer wrote
to a real, local LibreOffice (soffice --headless --convert-to fods) and inspects what
LibreOffice itself made of them — the strongest available signal that a write is genuinely
valid ODF, not just something our own reader happens to parse back. It skips automatically if
no soffice/libreoffice binary is on PATH.
Versions
Version numbers are derived automatically from git tags (via setuptools-scm) and follow
Semantic Versioning — while the major version stays 0, the API can
still change between minor versions. See
CHANGELOG.md (and the
Releases page) for what changed in each
version.
License
MIT — reuse with essentially no restriction, just keep the copyright notice.
Project name
odsslicer, to reflect the module's real differentiator — numpy-style indexing/slicing by
cell address — rather than a generic "ods reader".
Release files for odsslicer 0.12.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| odsslicer-0.12.0.tar.gz | 4.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| odsslicer-0.12.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.6 MB
Release files / odsslicer-0.12.0.tar.gz
| Download URL | odsslicer-0.12.0.tar.gz |
|---|---|
| Size | 4.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
22168297452112fc2cd3d830cd53c070812cc30dc29ab036527301369a47ad2d
|
|
BLAKE2b-256 checksum How to use checksums |
805ec63aecc070f9db155f5ce1e42d4e4b6be709de1415b15ab9bf2366713f08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / odsslicer-0.12.0-py3-none-any.whl
| Download URL | odsslicer-0.12.0-py3-none-any.whl |
|---|---|
| Size | 71.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1fad808c6d2020925c30f2476e8af25930a7babd425880d73103dedd76baef2a
|
|
BLAKE2b-256 checksum How to use checksums |
e6f29371cbf6124a4d5b454efa587883c212ec8f1762f689198536fc5d73959f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log