|
2 | 2 |
|
3 | 3 | ## Changes from 4.10.0 to 4.10.1 |
4 | 4 |
|
5 | | -XXX version-specific blurb XXX |
| 5 | +A correctness release: lazy indexing and reductions now follow NumPy in a |
| 6 | +batch of cases where they quietly did not, the stores close several |
| 7 | +cross-process read races, and wheels finally ship usable C-Blosc2 development |
| 8 | +files. Bundled C-Blosc2 moves to 3.3.2. |
| 9 | + |
| 10 | +### Bug fixes |
| 11 | + |
| 12 | +#### Lazy expressions and indexing |
| 13 | + |
| 14 | +- **Indexing a lazy expression with an integer squeezed too much.** |
| 15 | + `expr[0]` dropped *every* length-1 axis of the result, including ones the |
| 16 | + index kept, so a `(1, 4)` expression indexed at `[0]` came back as `(4,)` |
| 17 | + where NumPy gives `(4,)` only for the consumed axis and keeps the rest. |
| 18 | + Only the dimensions the integer indices actually consumed are dropped now. |
| 19 | + `where()` results, whose length is data-dependent, are left alone. |
| 20 | + Closes #319. |
| 21 | +- **`LazyUDF` and broadcast operands mis-indexed on `None`.** A `None` in the |
| 22 | + key inserts an axis in the result but consumes none in the operand; |
| 23 | + aligning it as if it did shifted every axis to its left by one, so |
| 24 | + `expr[None, 2]` read the wrong operand region. Closes #403, #688. |
| 25 | +- **Indexing a full reduction materialized the whole operand.** `(a + b).sum() |
| 26 | + [key]` evaluated the reduction over everything and then indexed; the |
| 27 | + operands are sliced first now. Closes #457. |
| 28 | +- **Datetime comparisons in lazy expressions raised.** numexpr has no datetime |
| 29 | + type, so `t1 < t2` on `datetime64`/`timedelta64` died with `unknown type |
| 30 | + datetime64[s]` and the error was re-raised rather than letting the NumPy |
| 31 | + fallback try. They are compared as their underlying int64 counts, which is |
| 32 | + exact, keeping the fast path. Mixed units and `NaT` decline that route and |
| 33 | + fall back to NumPy, since raw counts would silently lie about both. |
| 34 | + Closes #409. |
| 35 | +- **`NDArray.nbytes` reported the padded size.** It now returns the logical |
| 36 | + `size * itemsize`, matching NumPy, whenever the shape does not fill the |
| 37 | + chunk grid exactly. `cratio` still measures the stored (padded) data, so |
| 38 | + `nbytes / cbytes` need not equal `cratio`; use `.schunk.nbytes` for the |
| 39 | + padded figure. Closes #544. |
| 40 | + |
| 41 | +#### Tables and stores |
| 42 | + |
| 43 | +- **`CTable.where()` applied a short boolean mask to the wrong rows.** A mask |
| 44 | + shorter than the live-row count was padded out to the *physical* length, |
| 45 | + which aligned it with the underlying column and selected rows outside the |
| 46 | + view. A mask no longer than the live-row count is now treated as logical — |
| 47 | + entry *i* selects the *i*-th live row — with a short one simply leaving the |
| 48 | + trailing rows unselected. Closes #607. |
| 49 | +- **Cross-process read races in `EmbedStore` and `DictStore`.** Both resolved |
| 50 | + a key under the store lock but read the data after releasing it, so a |
| 51 | + concurrent writer could be caught mid-mutation: `EmbedStore.__getitem__` |
| 52 | + returned bytes from a stale offset, and `DictStore.__getitem__` opened an |
| 53 | + external leaf that a concurrent overwrite had just removed or half-rewritten |
| 54 | + (`KeyError`, or `RuntimeError: Error while getting the buffer`). The |
| 55 | + resolve, the existence check and the open now share one lock; non-shared |
| 56 | + stores skip it entirely. Fixes #691, #692. |
| 57 | +- **Overwriting an external `DictStore` leaf is now atomic.** `__setitem__` |
| 58 | + removed the old leaf and rebuilt it at the same path, and the handle |
| 59 | + `__getitem__` returns holds no file descriptor — the C layer re-opens the |
| 60 | + leaf by path for every chunk it decompresses — so a read already in flight |
| 61 | + could open a truncated file. The new leaf is built beside its final name and |
| 62 | + moved in with a single `os.replace()`, so every such re-open sees one |
| 63 | + complete cframe or the other. A crash mid-write now leaves a stray `.tmp` |
| 64 | + staging file rather than a partial leaf. |
| 65 | +- **`TreeStore.close()` swallowed inline handle failures.** A `CTable` that |
| 66 | + failed to flush left an archive whose row count disagreed with a varlen |
| 67 | + column, reported only on read, long after `close()` said it succeeded. Every |
| 68 | + handle still gets a chance to close and the store is still packed; the |
| 69 | + failure is then re-raised. |
| 70 | + |
| 71 | +#### Other |
| 72 | + |
| 73 | +- **`unpack_tensor()` turned padding into a phantom column.** `np.dtype()` |
| 74 | + renames the empty-named padding fields of a structured descr (`''` -> `f2`), |
| 75 | + so a packed tensor with padding came back with an extra field. Closes #287. |
| 76 | + |
| 77 | +### Packaging |
| 78 | + |
| 79 | +- **Wheels ship usable C-Blosc2 development files.** Install paths are now |
| 80 | + relative to `CMAKE_INSTALL_PREFIX`; the absolute ones made C-Blosc2 generate |
| 81 | + a `blosc2.pc` and exported targets pointing into the build tempdir, so |
| 82 | + `pkg-config` and `find_package(Blosc2)` both failed against an installed |
| 83 | + wheel. Verified by building and linking a C program against a wheel three |
| 84 | + ways: `pkg-config`, `Blosc2::blosc2_shared` and `Blosc2::blosc2_static`. |
| 85 | + miniexpr's license texts are mirrored into `.dist-info/licenses`, where PEP |
| 86 | + 639 tooling looks. Closes #627. |
| 87 | +- **Wheels carry two copies of `libblosc2` instead of three**, ~1.5 MB |
| 88 | + smaller. C-Blosc2 set both `VERSION` and `SOVERSION`, and scikit-build-core |
| 89 | + follows symlinks, so the fully versioned file — referenced by nothing but |
| 90 | + the symlinks pointing at it — was shipped as a third full copy. |
| 91 | + |
| 92 | +### Development |
| 93 | + |
| 94 | +- The test suite runs in parallel by default (`-n auto --dist loadfile` in |
| 95 | + `pytest.ini`), 130s -> 35s locally, falling back to a serial run when |
| 96 | + pytest-xdist is absent. The `heavy` tests, 58% of everything collected and |
| 97 | + excluded from every push-time job, now run in a nightly workflow. The |
| 98 | + network tests run once per push on a single Linux job instead of five times. |
| 99 | +- The ruff rule set is spelled out with `select` rather than `extend-select`, |
| 100 | + so a ruff release widening its defaults no longer redefines what CI |
| 101 | + enforces. |
6 | 102 |
|
7 | 103 | ## Changes from 4.9.1 to 4.10.0 |
8 | 104 |
|
|
0 commit comments