Metadata-Version: 2.4
Name: excelizarr
Version: 0.1.0
Summary: A cloud-native representation of an .xlsx workbook, in Zarr. Yes, really.
Author-email: Charles Turner <charles.turner@anu.edu.au>
Maintainer-email: Charles Turner <charles.turner@anu.edu.au>
License: Apache Software License 2.0
Project-URL: bugs, https://github.com/charles-turner-1/excelizarr/issues
Project-URL: homepage, https://github.com/charles-turner-1/excelizarr
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: openpyxl
Requires-Dist: pandas
Requires-Dist: zarr>=3.3
Provides-Extra: fast
Requires-Dist: python-calamine; extra == "fast"
Provides-Extra: icechunk
Requires-Dist: icechunk; extra == "icechunk"
Provides-Extra: dev
Requires-Dist: black; extra == "dev"
Requires-Dist: coverage; extra == "dev"
Requires-Dist: hypothesis; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=8; extra == "docs"
Requires-Dist: myst-nb>=1.2; extra == "docs"
Requires-Dist: pydata-sphinx-theme>=0.16; extra == "docs"
Requires-Dist: sphinx-design; extra == "docs"
Requires-Dist: sphinx-copybutton; extra == "docs"
Requires-Dist: ipykernel; extra == "docs"
Requires-Dist: icechunk; extra == "docs"
Dynamic: license-file

# excelizarr

**A cloud-native representation of an `.xlsx` workbook. In Zarr. Yes, really.**

> [!WARNING]
> This is a bit of fun. It works and it's property-tested, but please don't put it
> anywhere important. Claude wrote most of the code at my direction.

A friend made me realise you could save an `.xlsx` spreadsheet as an HDF5 file. That
raised an obvious question: why not Zarr? Then a workbook is chunked, compressed and
cloud-readable, and with [Icechunk](https://icechunk.io) it's version-controlled too.

```python
import excelizarr as xz

wb = xz.xlsx_to_zarr("model.xlsx", "s3://bucket/model.zarr")

wb["Inputs!B2:D10"]                                               # fetches a few chunks
df = xz.read_excel("s3://bucket/model.zarr", sheet_name="Inputs")  # == pd.read_excel
xz.zarr_to_xlsx("s3://bucket/model.zarr", "roundtrip.xlsx")       # formulas intact
```

## How it works

Each sheet becomes a zarr group of aligned, chunked (and sharded) 2D arrays:

- `kind`: which type each cell holds
- one array per value type: `number`, `string`, `bool`, `temporal`
- `formula` and `numfmt`

Zarr never writes a chunk that's all fill value, so unused types and empty space cost
nothing. A range read fetches only the chunks it overlaps, and the root's consolidated
metadata means opening a workbook is a single read. See
[the format](docs/guide/format.md).

- **`read_excel` parity.** `xz.read_excel` runs pandas' own Excel parser over the stored
  cells, so every keyword behaves the same, and a hypothesis property checks the frames
  match.
- **Round trips.** Formulas, number formats, merged cells, column widths, frozen panes
  and defined names all survive xlsx → zarr → xlsx.
- **Any zarr store.** Local, `s3://`, in memory, a `ZipStore`, or an Icechunk session.
- **In-place edits.** `wb["Inputs"]["B2"] = 42` rewrites the one shard that cell is in.

## Is it fast?

On a 50,000 × 20 sheet ([details](docs/guide/benchmarks.md)):

| read | seconds |
|---|---:|
| `pd.read_excel` (openpyxl) | 2.97 |
| `pd.read_excel` (calamine) | 0.63 |
| `pd.read_hdf` | **0.017** |
| `xz.read_excel` | 0.22 |
| 100-cell range read | **0.004** |

So HDF is still the right call for "the whole sheet as a DataFrame, fast". excelizarr
is 14× faster than parsing the `.xlsx`, smaller on disk than either, and much better
at the cloud-shaped jobs: range reads, partial access and version control.

From S3 (`ap-southeast-2`, from Australia), on a 500,000 × 20 sheet
([details](docs/guide/cloud.md#whole-sheets)):

| read | seconds |
|---|---:|
| `pd.read_excel` (openpyxl) | 34.76 |
| `pd.read_excel` (calamine) | 8.26 |
| download + `pd.read_hdf` | **1.99** |
| `xz.read_excel` | 2.96 |
| open + 100-cell range read | **0.48** |

The range read costs the same at 10,000 rows as at 500,000: it only ever fetches the
chunks it needs.

## Development

```bash
pixi run test             # hypothesis-heavy test suite
pixi run -e docs docs     # executed MyST docs, warnings are errors
pixi run -e bench bench   # the benchmark above
```

## Licence

[The Not Fit For Purpose Licence](LICENSE), which is the Apache License 2.0 with a
preamble. This tool is not fit for purpose. I don't care what your purpose is, this
isn't it.
