python-calamine-reducto 0.7.17__tar.gz → 0.7.19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/.github/workflows/CI.yml +45 -1
  2. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/CLAUDE.md +4 -3
  3. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/Cargo.lock +2 -2
  4. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/Cargo.toml +1 -1
  5. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/PKG-INFO +1 -1
  6. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/README.md +22 -25
  7. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/python/python_calamine/__init__.py +0 -2
  8. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/python/python_calamine/_python_calamine.pyi +166 -0
  9. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/lib.rs +3 -6
  10. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/types/errors.rs +21 -0
  11. python_calamine_reducto-0.7.19/src/types/grid.rs +1320 -0
  12. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/types/mod.rs +1 -1
  13. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/types/workbook.rs +1009 -645
  14. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/create_test_fixture.py +1 -1
  15. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_band.py +19 -1
  16. python_calamine_reducto-0.7.19/tests/test_bounded_reads.py +195 -0
  17. python_calamine_reducto-0.7.19/tests/test_gap_compression.py +258 -0
  18. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_grid.py +87 -486
  19. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_tables.py +1 -3
  20. python_calamine_reducto-0.7.19/tests/test_trim_header_only.py +133 -0
  21. python_calamine_reducto-0.7.19/tests/test_uncached_formulas.py +88 -0
  22. python_calamine_reducto-0.7.19/tests/xlsx_builder.py +57 -0
  23. python_calamine_reducto-0.7.17/src/types/grid.rs +0 -2141
  24. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/.github/workflows/release.yml +0 -0
  25. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/.gitignore +0 -0
  26. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/.pre-commit-config.yaml +0 -0
  27. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/.python-version +0 -0
  28. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/LICENSE +0 -0
  29. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/build.rs +0 -0
  30. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/pyproject.toml +0 -0
  31. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/python/python_calamine/py.typed +0 -0
  32. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/renovate.json +0 -0
  33. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/types/cell.rs +0 -0
  34. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/types/sheet.rs +0 -0
  35. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/src/types/table.rs +0 -0
  36. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/__init__.py +0 -0
  37. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/conftest.py +0 -0
  38. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/create_empty_merge_fixture.py +0 -0
  39. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/create_hidden_col_range_fixture.py +0 -0
  40. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/create_richtext_fixture.py +0 -0
  41. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/any_sheets.ods +0 -0
  42. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/any_sheets.xls +0 -0
  43. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/any_sheets.xlsb +0 -0
  44. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/any_sheets.xlsx +0 -0
  45. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/base.ods +0 -0
  46. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/base.xls +0 -0
  47. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/base.xlsb +0 -0
  48. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/base.xlsx +0 -0
  49. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/dropdown_test.xlsx +0 -0
  50. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/empty_file.ods +0 -0
  51. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/empty_file.xlsb +0 -0
  52. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/empty_file.xlsx +0 -0
  53. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/empty_merge_test.xlsx +0 -0
  54. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/formula_test.xlsx +0 -0
  55. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/grid_test.xlsx +0 -0
  56. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/hidden_col_range_test.xlsx +0 -0
  57. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/image_test.xlsx +0 -0
  58. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/issue139.xlsx +0 -0
  59. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/multi_image_test.xlsx +0 -0
  60. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/password.ods +0 -0
  61. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/password.xls +0 -0
  62. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/password.xlsb +0 -0
  63. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/password.xlsx +0 -0
  64. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/richtext_test.xlsx +0 -0
  65. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/table-multiple.xlsb +0 -0
  66. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/table-multiple.xlsx +0 -0
  67. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/data/temperature-table.xlsb +0 -0
  68. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_base.py +0 -0
  69. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_images.py +0 -0
  70. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_pandas.py +0 -0
  71. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/tests/test_sheet_type.py +0 -0
  72. {python_calamine_reducto-0.7.17 → python_calamine_reducto-0.7.19}/uv.lock +0 -0
@@ -53,10 +53,54 @@ jobs:
53
53
 
54
54
  - run: pytest
55
55
 
56
+ lint:
57
+ name: rust lint and unit tests
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v6
61
+
62
+ - name: configure git for private deps
63
+ run: git config --global url."https://x-access-token:${{ secrets.GH_PAT }}@github.com/".insteadOf "https://github.com/"
64
+
65
+ - name: install rust stable
66
+ uses: dtolnay/rust-toolchain@master
67
+ with:
68
+ toolchain: stable
69
+ components: clippy, rustfmt
70
+
71
+ - name: cache rust
72
+ uses: Swatinem/rust-cache@v2
73
+ with:
74
+ key: v3
75
+
76
+ - name: set up python
77
+ uses: actions/setup-python@v6
78
+ with:
79
+ python-version: "3.13"
80
+
81
+ - run: cargo fmt --check
82
+
83
+ - run: cargo clippy --all-targets -- -D warnings
84
+ env:
85
+ CARGO_NET_GIT_FETCH_WITH_CLI: "true"
86
+
87
+ - run: cargo test
88
+ env:
89
+ CARGO_NET_GIT_FETCH_WITH_CLI: "true"
90
+
91
+ - run: pip install numpy "mypy~=1.19.0"
92
+
93
+ - run: pip install -e .
94
+ env:
95
+ CARGO_NET_GIT_FETCH_WITH_CLI: "true"
96
+
97
+ - name: stubtest
98
+ run: python -m mypy.stubtest python_calamine
99
+
56
100
  # https://github.com/marketplace/actions/alls-green#why used for branch protection checks
57
101
  check:
58
102
  if: always()
59
- needs: [test]
103
+ needs: [test, lint]
60
104
  runs-on: ubuntu-latest
61
105
  steps:
62
106
  - name: Decide whether the needed jobs succeeded or failed
@@ -100,13 +100,14 @@ python tests/create_test_fixture.py
100
100
 
101
101
  | Path | What |
102
102
  |------|------|
103
- | `src/types/grid.rs` | CellGrid, CellProxy, CellGridView — the core Reducto addition (~1500 lines) |
104
- | `src/types/workbook.rs` | get_sheet_grid, get_sheet_styles, get_sheet_layout, get_sheet_bounds |
103
+ | `src/types/grid.rs` | CellGrid, CellProxy — the core Reducto addition |
104
+ | `src/types/workbook.rs` | CalamineWorkbook: grid builders, bounds, layout, formulas |
105
105
  | `src/types/mod.rs` | Module exports |
106
106
  | `src/lib.rs` | PyO3 module registration |
107
107
  | `python/python_calamine/__init__.py` | Python re-exports |
108
108
  | `Cargo.toml` | Points calamine + ssfmt deps to reductoai forks |
109
- | `tests/test_grid.py` | 86 tests for all CellGrid APIs |
109
+ | `tests/test_grid.py` | CellGrid API tests |
110
+ | `tests/test_bounded_reads.py` | Reads whose cost must follow the filled cells |
110
111
 
111
112
  ## Architecture
112
113
 
@@ -80,7 +80,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
80
80
  [[package]]
81
81
  name = "calamine"
82
82
  version = "0.32.0"
83
- source = "git+https://github.com/reductoai/calamine.git?branch=main#9e6866a552b5baa5b9f2270049eeb72846c4386e"
83
+ source = "git+https://github.com/reductoai/calamine.git?branch=main#297ee32c41c7449e22f9fb94e93c0291b0b4d15d"
84
84
  dependencies = [
85
85
  "atoi_simd",
86
86
  "byteorder",
@@ -612,7 +612,7 @@ dependencies = [
612
612
 
613
613
  [[package]]
614
614
  name = "python-calamine-reducto"
615
- version = "0.7.17"
615
+ version = "0.7.19"
616
616
  dependencies = [
617
617
  "calamine",
618
618
  "chrono",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "python-calamine-reducto"
3
- version = "0.7.17"
3
+ version = "0.7.19"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-calamine-reducto
3
- Version: 0.7.17
3
+ Version: 0.7.19
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -15,43 +15,40 @@ Our additions live on `main`.
15
15
 
16
16
  ## What we added
17
17
 
18
- ### Style & layout bindings (`workbook.rs`)
19
-
20
- - **`get_sheet_styles(name)`** → list of `(row, col, style_dict)` tuples
21
- - Font: bold, italic, size, color, name, underline, strikethrough
22
- - Fill: pattern, fg_color, bg_color
23
- - Borders: top, bottom, left, right styles
24
- - Number format string
18
+ ### Workbook bindings (`workbook.rs`)
19
+
20
+ - **`get_sheet_bounds(name)`** → `(max_filled_row, max_filled_col)`. xlsx streams the cells.
21
+ - **`get_sheet_grid_streaming(name, include_styles=True, ...)`** →
22
+ `(grid, declared_rows, declared_cols, dropped_cells)`. Streams xlsx cells with no dense
23
+ range and trims sparse outlier cells.
24
+ - **`for_each_band(name, band_rows, callback, ...)`** → streams the sheet as row bands.
25
+ - **`get_sheet_grid(name, rows, cols, include_styles=True)`** → `CellGrid` from the dense range.
25
26
  - **`get_sheet_layout(name)`** → dict with row heights, column widths, defaults
26
- - **`get_sheet_bounds(name)`** → `(max_filled_row, max_filled_col)` computed in Rust
27
- - **`get_sheet_grid(name, rows, cols, include_styles=True)`** → `CellGrid`
28
- (builds entire grid in a single Rust call)
27
+ - **`get_sheet_formulas`**, **`get_sheet_merged_ranges`**, **`get_sheet_images`**,
28
+ **`get_sheet_data_validations`**, **`get_sheet_freeze_panes`**,
29
+ **`get_sheet_filled_count`**, **`get_sheet_uncached_formula_count`**,
30
+ **`get_all_tables_metadata`**
29
31
 
30
32
  ### CellGrid (`grid.rs`) — Rust-backed 2D grid
31
33
 
32
- Replaces numpy structured arrays. All cell data stays in Rust Vecs and only
33
- crosses to Python on demand.
34
+ All cell data stays in Rust. The API converts a grid to a numpy structured array once,
35
+ with `fill_numpy_array`.
34
36
 
35
37
  **Types:**
36
- - `CellGrid` — the main 2D grid (values, styles, merges, header, colors, formulas)
37
- - `CellProxy` — lightweight single-cell accessor (supports `cell.value` and `cell["value"]`)
38
- - `CellGridView` — zero-copy sub-grid view for slicing
38
+ - `CellGrid` — the main 2D grid (values, styles, merges, header, colors, formulas).
39
+ Styles and color names are stored as indexes into small palettes.
40
+ - `CellProxy` — single-cell accessor (`cell["value"]`)
39
41
 
40
42
  **Access patterns:**
41
43
  - `grid[r, c]` → `CellProxy`
42
- - `grid[r1:r2, c1:c2]` → `CellGridView`
43
44
  - `grid["field_name"]` → numpy 2D array of that field
44
45
  - `grid["field_name"] = value` → broadcast or copy from numpy
45
-
46
- **Grid operations:**
47
- - `vstack(other)` / `hstack(other)` — concatenation
48
- - `slice_copy(r1, r2, c1, c2)` — independent copy of region
49
- - `copy()` — deep copy
50
46
  - `CellGrid.empty(rows, cols)` — create empty grid
51
- - `masked_copy_from(source, mask, ...)` — boolean mask copy
52
47
 
53
48
  **Formatting:**
54
- - `apply_formatting(callback)` — calls Python SSF per cell
49
+ - `apply_formatting_rust()` — SSF number formats in Rust
50
+ - `apply_dateutil_reformat(date_parse)`, `apply_color_names(converter)`,
51
+ `propagate_merged_values()`
55
52
  - `apply_raw_as_formatted()` — `str(raw)` for large sheets
56
53
 
57
54
  ### Performance
@@ -88,8 +85,8 @@ git merge upstream/master
88
85
 
89
86
  | File | What |
90
87
  |------|------|
91
- | `src/types/grid.rs` | CellGrid, CellProxy, CellGridView |
92
- | `src/types/workbook.rs` | get_sheet_grid, get_sheet_styles, get_sheet_layout |
88
+ | `src/types/grid.rs` | CellGrid, CellProxy |
89
+ | `src/types/workbook.rs` | CalamineWorkbook: grid builders, bounds, layout, formulas |
93
90
  | `src/types/sheet.rs` | CalamineSheet, CalamineCellIterator |
94
91
  | `src/types/cell.rs` | CellValue enum → Python type conversion |
95
92
  | `src/lib.rs` | PyO3 module registration |
@@ -4,7 +4,6 @@ from ._python_calamine import (
4
4
  CalamineTable,
5
5
  CalamineWorkbook,
6
6
  CellGrid,
7
- CellGridView,
8
7
  CellProxy,
9
8
  PasswordError,
10
9
  SheetMetadata,
@@ -26,7 +25,6 @@ __all__ = (
26
25
  "CalamineTable",
27
26
  "CalamineWorkbook",
28
27
  "CellGrid",
29
- "CellGridView",
30
28
  "CellProxy",
31
29
  "PasswordError",
32
30
  "SheetMetadata",
@@ -6,6 +6,10 @@ import enum
6
6
  import os
7
7
  import types
8
8
  import typing
9
+ from collections.abc import Callable
10
+
11
+ import numpy as np
12
+ import numpy.typing as npt
9
13
 
10
14
  @typing.type_check_only
11
15
  class ReadBuffer(typing.Protocol):
@@ -174,6 +178,100 @@ class CalamineTable:
174
178
  ]:
175
179
  """Returning data from table as list of lists."""
176
180
 
181
+ @typing.type_check_only
182
+ class TableMetadata(typing.TypedDict):
183
+ name: str
184
+ sheet: str
185
+ columns: list[str]
186
+ start: tuple[int, int]
187
+ """First data row and column, 0-based (the header row is above it)."""
188
+ end: tuple[int, int]
189
+
190
+ @typing.type_check_only
191
+ class SheetLayout(typing.TypedDict):
192
+ column_widths: list[tuple[int, float, bool]]
193
+ """`(col, width, hidden)` for each column with a `<col>` entry."""
194
+ row_heights: list[tuple[int, float, bool]]
195
+ """`(row, height, hidden)` for each row with a custom height or flag."""
196
+ default_column_width: float | None
197
+ default_row_height: float | None
198
+
199
+ @typing.type_check_only
200
+ class DataValidation(typing.TypedDict):
201
+ type: str
202
+ sqref: str
203
+ formula1: str | None
204
+ formula2: str | None
205
+ allow_blank: bool
206
+
207
+ @typing.final
208
+ class CellProxy:
209
+ """One cell of a `CellGrid`. Read a field with `cell["value"]`."""
210
+
211
+ def __getitem__(self, key: str, /) -> typing.Any: ...
212
+
213
+ @typing.final
214
+ class CellGrid:
215
+ """A sheet grid held in Rust. The API converts it to numpy with `fill_numpy_array`."""
216
+
217
+ @property
218
+ def shape(self) -> tuple[int, int]: ...
219
+ @property
220
+ def size(self) -> int: ...
221
+ @property
222
+ def row_coords(self) -> npt.NDArray[np.int32]:
223
+ """Grid row -> 0-based sheet row: `arange` plus the band top, unless gap compression engaged."""
224
+
225
+ @property
226
+ def col_coords(self) -> npt.NDArray[np.int32]:
227
+ """Grid column -> 0-based sheet column."""
228
+
229
+ @property
230
+ def uncached_formula_count(self) -> int | None:
231
+ """Formula cells with no cached value (streaming xlsx grids), else `None`."""
232
+
233
+ def __len__(self) -> int: ...
234
+ @classmethod
235
+ def empty(cls, rows: int, cols: int) -> CellGrid:
236
+ """An empty grid whose `merge_parent` is `(-1, -1)` for every cell."""
237
+
238
+ @typing.overload
239
+ def __getitem__(self, key: str, /) -> npt.NDArray[typing.Any]:
240
+ """The numpy array of one field, e.g. `grid["value"]`."""
241
+
242
+ @typing.overload
243
+ def __getitem__(self, key: tuple[int, int], /) -> CellProxy: ...
244
+ def __setitem__(
245
+ self,
246
+ key: typing.Literal["table_member", "header", "value"],
247
+ value: bool | npt.NDArray[np.bool_] | None,
248
+ /,
249
+ ) -> None: ...
250
+ def set_cell_formula(self, row: int, col: int, formula: str) -> None: ...
251
+ def set_cell_value(self, row: int, col: int, value: str) -> None: ...
252
+ def set_field_range(
253
+ self,
254
+ field: typing.Literal["table_member", "header"],
255
+ r1: int,
256
+ r2: int,
257
+ c1: int,
258
+ c2: int,
259
+ value: bool,
260
+ ) -> None: ...
261
+ def apply_formatting_rust(self) -> None:
262
+ """Format each value with its number format (SSF, in Rust)."""
263
+
264
+ def apply_dateutil_reformat(self, date_parse: Callable[[str], float | None]) -> None:
265
+ """Reformat text cells that have a date format. `date_parse` gives a date serial."""
266
+
267
+ def apply_raw_as_formatted(self) -> None: ...
268
+ def apply_color_names(self, converter: Callable[[str], str | None]) -> None:
269
+ """Set `cell_textcolor` and `cell_highlight` from the hex colors of each style."""
270
+
271
+ def propagate_merged_values(self) -> None: ...
272
+ def fill_numpy_array(self, arr: npt.NDArray[np.void]) -> None:
273
+ """Move every field into `arr`. The grid is empty after this call."""
274
+
177
275
  @typing.final
178
276
  class CalamineWorkbook:
179
277
  path: str | None
@@ -276,6 +374,72 @@ class CalamineWorkbook:
276
374
  TableNotFound: If table not found in workbook.
277
375
  """
278
376
 
377
+ def get_all_tables_metadata(self) -> list[TableMetadata]: ...
378
+ def get_sheet_layout(self, name: str) -> SheetLayout: ...
379
+ def get_sheet_grid(
380
+ self,
381
+ name: str,
382
+ max_filled_row: int,
383
+ max_filled_col: int,
384
+ include_styles: bool = True,
385
+ ) -> CellGrid:
386
+ """Build a grid from the dense range. Bounds of 0 compute them from the data."""
387
+
388
+ def get_sheet_grid_streaming(
389
+ self,
390
+ name: str,
391
+ include_styles: bool = True,
392
+ trigger_cells: int = 2_000_000,
393
+ trigger_density: float = 0.001,
394
+ coverage: float = 0.999,
395
+ trim_header_only: bool = False,
396
+ max_empty_run: int | None = None,
397
+ ) -> tuple[CellGrid, int, int, int]:
398
+ """Stream a grid. Returns `(grid, declared_rows, declared_cols, dropped_cells)`.
399
+
400
+ `trim_header_only` also drops the trailing columns whose only value is in the header row.
401
+ `max_empty_run` keeps at most that many rows (and columns) of each empty run inside the
402
+ trimmed bounds. It engages only above `trigger_cells`, and only when it halves the grid.
403
+ `coord` and `merge_parent` stay in sheet positions; `row_coords`/`col_coords` map them.
404
+ """
405
+
406
+ def get_sheet_filled_count(self, name: str) -> tuple[int, int, int]:
407
+ """`(filled, max_filled_row, max_filled_col)` in one streaming pass. xlsx only."""
408
+
409
+ def get_sheet_uncached_formula_count(self, name: str) -> int | None: ...
410
+ def for_each_band(
411
+ self,
412
+ name: str,
413
+ band_rows: int,
414
+ callback: Callable[[CellGrid, int], object],
415
+ include_styles: bool = True,
416
+ trigger_cells: int = 2_000_000,
417
+ trigger_density: float = 0.001,
418
+ coverage: float = 0.999,
419
+ propagate_merges: bool = False,
420
+ trim_header_only: bool = False,
421
+ ) -> tuple[int, int, int]:
422
+ """Stream the sheet as row bands. Returns `(declared_rows, declared_cols, dropped_cells)`."""
423
+
424
+ def get_sheet_formulas(self, name: str) -> list[tuple[int, int, str]]:
425
+ """`(row, col, formula)` for each formula cell. Each formula starts with `=`."""
426
+
427
+ def get_sheet_images(self, name: str) -> list[tuple[bytes, str, int, int]]:
428
+ """`(data, extension, anchor_row, anchor_col)` for each image."""
429
+
430
+ def get_sheet_data_validations(self, name: str) -> list[DataValidation]: ...
431
+ def get_sheet_freeze_panes(self, name: str) -> tuple[int, int]:
432
+ """`(frozen_rows, frozen_cols)`."""
433
+
434
+ def get_sheet_merged_ranges(self, name: str) -> list[tuple[int, int, int, int]]:
435
+ """`(start_row, start_col, end_row, end_col)`, 0-based and inclusive."""
436
+
437
+ def get_sheet_bounds(self, name: str) -> tuple[int, int]:
438
+ """`(max_filled_row, max_filled_col)`, 1-based and exclusive.
439
+
440
+ xlsx does not count `""` cells, as `get_sheet_grid_streaming`. Other formats count them.
441
+ """
442
+
279
443
  class CalamineError(Exception): ...
280
444
  class PasswordError(CalamineError): ...
281
445
  class WorksheetNotFound(CalamineError): ...
@@ -301,6 +465,8 @@ __all__ = [
301
465
  "CalamineSheet",
302
466
  "CalamineTable",
303
467
  "CalamineWorkbook",
468
+ "CellGrid",
469
+ "CellProxy",
304
470
  "PasswordError",
305
471
  "SheetMetadata",
306
472
  "SheetTypeEnum",
@@ -2,10 +2,9 @@ use pyo3::prelude::*;
2
2
 
3
3
  mod types;
4
4
  use crate::types::{
5
- CalamineError, CalamineSheet, CalamineTable, CalamineWorkbook, CellGrid, CellGridIterator,
6
- CellGridView, CellProxy, CellValue, Error, PasswordError, SheetMetadata, SheetTypeEnum,
7
- SheetVisibleEnum, TableNotFound, TablesNotLoaded, TablesNotSupported, WorkbookClosed,
8
- WorksheetNotFound, XmlError, ZipError,
5
+ CalamineError, CalamineSheet, CalamineTable, CalamineWorkbook, CellGrid, CellProxy, CellValue,
6
+ Error, PasswordError, SheetMetadata, SheetTypeEnum, SheetVisibleEnum, TableNotFound,
7
+ TablesNotLoaded, TablesNotSupported, WorkbookClosed, WorksheetNotFound, XmlError, ZipError,
9
8
  };
10
9
 
11
10
  #[pyfunction]
@@ -29,8 +28,6 @@ fn _python_calamine(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
29
28
  m.add_class::<CalamineTable>()?;
30
29
  m.add_class::<CellGrid>()?;
31
30
  m.add_class::<CellProxy>()?;
32
- m.add_class::<CellGridView>()?;
33
- m.add_class::<CellGridIterator>()?;
34
31
  m.add("CalamineError", py.get_type::<CalamineError>())?;
35
32
  m.add("PasswordError", py.get_type::<PasswordError>())?;
36
33
  m.add("WorksheetNotFound", py.get_type::<WorksheetNotFound>())?;
@@ -20,6 +20,24 @@ create_exception!(python_calamine, TablesNotSupported, CalamineError);
20
20
  create_exception!(python_calamine, TablesNotLoaded, CalamineError);
21
21
  create_exception!(python_calamine, TableNotFound, CalamineError);
22
22
 
23
+ impl From<CalamineCrateError> for Error {
24
+ fn from(err: CalamineCrateError) -> Self {
25
+ Error::Calamine(err)
26
+ }
27
+ }
28
+
29
+ impl From<XlsxError> for Error {
30
+ fn from(err: XlsxError) -> Self {
31
+ Error::Calamine(err.into())
32
+ }
33
+ }
34
+
35
+ impl From<XlsbError> for Error {
36
+ fn from(err: XlsbError) -> Self {
37
+ Error::Calamine(err.into())
38
+ }
39
+ }
40
+
23
41
  impl From<Error> for PyErr {
24
42
  fn from(val: Error) -> Self {
25
43
  match val {
@@ -52,6 +70,7 @@ impl From<Error> for PyErr {
52
70
  XlsxError::XmlEof(error) => XmlError::new_err(error.to_string()),
53
71
  XlsxError::Password => PasswordError::new_err(err.to_string()),
54
72
  XlsxError::TableNotFound(error) => TableNotFound::new_err(error.to_string()),
73
+ XlsxError::TablesNotLoaded => TablesNotLoaded::new_err(err.to_string()),
55
74
  XlsxError::WorksheetNotFound(error) => {
56
75
  WorksheetNotFound::new_err(error.to_string())
57
76
  }
@@ -63,6 +82,8 @@ impl From<Error> for PyErr {
63
82
  XlsbError::Xml(error) => XmlError::new_err(error.to_string()),
64
83
  XlsbError::XmlAttr(error) => XmlError::new_err(error.to_string()),
65
84
  XlsbError::Password => PasswordError::new_err(err.to_string()),
85
+ XlsbError::TableNotFound(error) => TableNotFound::new_err(error.to_string()),
86
+ XlsbError::TablesNotLoaded => TablesNotLoaded::new_err(err.to_string()),
66
87
  XlsbError::WorksheetNotFound(error) => {
67
88
  WorksheetNotFound::new_err(error.to_string())
68
89
  }