Metadata-Version: 2.4
Name: johnhenry-math-plus-interop
Version: 0.0.0
Summary: Python-side interop helpers for math-plus: Arrow IPC / Parquet / .npy-.npz round-trips with pandas/NumPy (issue #21)
License: MIT
Project-URL: Repository, https://github.com/johnhenry/math-plus
Project-URL: Homepage, https://github.com/johnhenry/math-plus/tree/main/packages/interop-python
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: pyarrow>=18.0.0
Requires-Dist: pandas>=2.2.0
Requires-Dist: numpy>=1.26.0
Provides-Extra: test
Requires-Dist: pytest>=8.0.0; extra == "test"

# johnhenry-math-plus-interop

[![PyPI version](https://img.shields.io/pypi/v/johnhenry-math-plus-interop.svg)](https://pypi.org/project/johnhenry-math-plus-interop/)
[![license](https://img.shields.io/pypi/l/johnhenry-math-plus-interop.svg)](../../LICENSE)

The Python-side half of [math-plus](https://github.com/johnhenry/math-plus)'s interop story (issue #21). Ships to **PyPI as `johnhenry-math-plus-interop`** (not npm) — install alongside `pyarrow`/`pandas`.

This package lives at `packages/interop-python/` in the math-plus repo but is **outside both the npm and Cargo workspaces** (it has no `package.json`) — released separately, on its own PyPI cadence, using `pyproject.toml`/`setuptools` rather than Changesets.

## Install

```bash
pip install johnhenry-math-plus-interop
```

## Usage

```python
from math_plus_interop import read_ipc, write_ipc, read_parquet, write_parquet, load_npy, save_npy

df = read_ipc("data.arrow")               # -> pandas.DataFrame
write_ipc(df, "out.arrow")

df = read_parquet("data.parquet", columns=["a", "b"], filters=[("a", ">", 10)])
write_parquet(df, "out.parquet", compression="zstd")  # pyarrow ships a real zstd encoder natively

arr = load_npy("weights.npy")
save_npy("weights.npy", arr)
```

`.npy`/`.npz` helpers (`load_npy`/`save_npy`/`load_npz`/`save_npz`) are thin — NumPy already owns that format on the Python side; they exist for API-surface symmetry with @johnhenry/math-plus-tensor-core's own `.npy` reader/writer, not because `numpy.load`/`numpy.save` need wrapping.

## Explicitly out of scope

- **The Arrow C Data Interface / PyCapsule zero-copy bridge.** Deferred — it only pays off with a native Node addon in the same process, which is downstream of math-plus's WASM work.
- **Python-identical float text formatting (`repr(float)`, `%g`, `json.dumps`) on the JS side.** Not needed: every interop path here is binary (Arrow IPC, Parquet, `.npy`/`.npz`), so floats never cross the language boundary as text, and on this side Python formats its own values. `@johnhenry/math-plus-scalar-types` only re-exports `@johnhenry/math` scalar classes and has no text output. If a JS surface ever has to produce Python-identical text, depend on [`@johnhenry/pyjson`](https://www.npmjs.com/package/@johnhenry/pyjson), the canonical implementation. Do not write a second copy (issue #128).
- **Python's `__dataframe__()` DataFrame Interchange Protocol.** Rejected: pandas deprecates it and drops the fallback entirely in pandas 4.0. Arrow IPC is the portable interop format across browser/Node/Deno/Python.

## Development

```bash
pip install -e ".[test]"
pytest tests/
```

The test suite needs `pyarrow`/`pandas`/`numpy` (installed automatically via the package's own dependencies + the `test` extra). On NixOS (no system `pip`), use the same pattern the rest of the math-plus repo uses for its Python oracles (see [`../../docs/TESTING.md`](../../docs/TESTING.md)):

```bash
nix-shell -p "python3.withPackages(ps: [ps.pyarrow ps.pandas ps.numpy ps.pytest])" \
  --run "cd packages/interop-python && PYTHONPATH=src python3 -m pytest tests/ -v"
```

### Bidirectional conformance fixtures

`tests/fixtures/` holds small, **committed** binary fixtures proving both interop directions actually work — regenerating live Python/JS processes on every test run would make the Python suite depend on Node being present (and vice versa), so fixtures are generated once and checked in, matching this repo's established convention (see `packages/frame-parquet/test/fixtures/`).

- **JS writes, Python reads**: `js_written.arrow`/`js_written.parquet`/`js_written_expected.json`, generated by `fixtures/generate_js_fixtures.mjs` (uses `@johnhenry/math-plus-frame-arrow`/`@johnhenry/math-plus-frame-parquet`). Verified by `tests/test_ipc_conformance.py`/`tests/test_parquet_conformance.py`.
- **Python writes, JS reads**: `py_written.arrow`/`py_written.parquet`/`py_written.npy`/`py_written.npz`/`py_written_expected.json`, generated by `fixtures/generate_py_fixtures.py` (uses this package's own `write_ipc`/`write_parquet`/`save_npy`/`save_npz`). Verified both by this package's own tests (self-consistency) AND by two JS-side tests in the sibling packages that actually do the reading: `../frame-arrow/test/interop-python.test.ts` and `../frame-parquet/test/interop-python.test.ts` — real cross-package, cross-language verification, not just two suites separately trusting their own output.

To regenerate (only needed if the fixture *data* itself needs to change — a schema/version bump, a new dtype to cover, etc.):

```bash
# From the repo root:
node packages/interop-python/tests/fixtures/generate_js_fixtures.mjs
nix-shell -p "python3.withPackages(ps: [ps.pyarrow ps.pandas ps.numpy])" \
  --run "python3 packages/interop-python/tests/fixtures/generate_py_fixtures.py"
```
