Skip to content

Commit 3864a2d

Browse files
committed
Getting ready for release 4.4.3
1 parent 594abf6 commit 3864a2d

7 files changed

Lines changed: 145 additions & 59 deletions

File tree

ANNOUNCE.rst

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,61 @@
1-
Announcing Python-Blosc2 4.4.2
1+
Announcing Python-Blosc2 4.4.3
22
==============================
33

4-
We are happy to announce this feature and maintenance release that promotes
5-
DSL kernels to first-class CTable computed columns, adds a convenient new
6-
column-assignment API, speeds up bulk NDArray writes, and fixes several
7-
correctness issues.
4+
We are happy to announce this maintenance release that makes CTable
5+
cold-start, printing, querying and groupby noticeably faster, trims the
6+
memory footprint of ``import blosc2``, adds raw-storage access for columns,
7+
and exposes the new JPEG 2000 codec plugins.
88

99
The main highlights are:
1010

11-
- **DSL kernels as first-class CTable columns**: ``@blosc2.jit``-decorated
12-
functions can now back virtual computed columns, stored generated columns,
13-
and ``where()`` filter predicates directly — in addition to the existing
14-
string-expression form. Columns survive save/open round-trips via
15-
persisted source.
11+
- **Faster CTable cold-start**: views created by ``select()`` now open
12+
columns lazily — only the columns actually read are loaded from storage —
13+
and queries only open the SUMMARY indexes referenced by the predicate,
14+
instead of every indexed column on a wide persistent table.
1615

17-
- **New ``t["col"] = arr`` assignment**: a clean shorthand for overwriting all
18-
live rows of a column. Accepts any array-like, including ``blosc2.NDArray``.
16+
- **Faster printing and groupby**: table rendering now performs a single
17+
combined sparse read per column (instead of ~6), and groupby takes the
18+
dense fast path for float key columns whose values are integral and fit a
19+
compact non-negative range.
1920

20-
- **Chunked NDArray writes**: passing a ``blosc2.NDArray`` to ``extend()`` or
21-
``col[:] = arr`` now decompresses one chunk at a time instead of loading
22-
the entire array into memory, keeping peak RSS bounded for large columns.
21+
- **Lighter imports**: the on-disk chunk prefetcher no longer uses asyncio,
22+
so ``import blosc2`` skips ~30 asyncio modules and saves ~3 MB of memory.
23+
A latent prefetcher deadlock on early iterator close was fixed as well.
2324

24-
- **``BLOSC_ME_JIT`` full override**: the environment variable now takes
25-
unconditional priority over both ``jit=`` and ``jit_backend=`` keyword
26-
arguments, making backend switching from the command line effortless.
25+
- **New ``Column.raw`` accessor**: the underlying storage container
26+
(``NDArray``, ``ListArray``, …) as a blosc2-native compressed object —
27+
unlike ``col[...]`` reads, which always materialize NumPy arrays. Useful
28+
as a lazy-expression operand and for storage introspection.
2729

28-
- **Correctness fixes**: a ``None == None`` guard bug that could corrupt rows
29-
when writing to a view-backed column via the NDArray fast path; a missing
30-
view guard in the new ``__setitem__``; and the fast path being silently
31-
disabled for disk-opened tables have all been fixed.
30+
- **J2K and HTJ2K codecs**: ``blosc2.Codec.J2K`` and ``blosc2.Codec.HTJ2K``
31+
expose the IDs for the new JPEG 2000 plugins (``pip install blosc2-j2k``
32+
/ ``pip install blosc2-htj2k``). Also, C-Blosc2 has been updated to 3.1.3.
3233

33-
A quick example of the new DSL computed column API::
34+
- **Fixes**: ``--float-trunc-prec`` in the ``parquet_to_blosc2`` CLI now
35+
propagates to nested columns; unsupported computed-column expressions are
36+
rejected early with an actionable error; and opening a ``.b2z``/``.b2d``
37+
store in read mode no longer creates a temporary directory.
38+
39+
A quick example of the new ``Column.raw`` accessor::
3440

3541
import blosc2
3642
import dataclasses
37-
import numpy as np
3843

3944
@dataclasses.dataclass
4045
class Row:
4146
price: float = blosc2.field(blosc2.float64())
4247
qty: int = blosc2.field(blosc2.int64())
4348

44-
@blosc2.dsl_kernel
45-
def revenue(price, qty):
46-
return price * qty
47-
4849
t = blosc2.CTable(Row, new_data=[(1.5, 10), (2.0, 5), (3.0, 3)])
49-
t.add_computed_column("revenue", revenue, inputs=["price", "qty"])
50-
print(t["revenue"][:]) # array([15., 10., 9.])
5150

52-
# Column assignment with a blosc2.NDArray — written chunk-by-chunk
53-
new_prices = blosc2.array([1.0, 2.5, 4.0])
54-
t["price"] = new_prices
51+
# The raw storage container, as a blosc2-native compressed object.
52+
# It is over-allocated to chunk capacity, so slice to the live row count.
53+
raw = t["price"].raw # a blosc2.NDArray
54+
print(raw[:len(t)]) # [1.5 2. 3. ]
55+
56+
# Usable directly as a lazy-expression operand, without decompressing
57+
expr = raw * 2.0
58+
print(expr[:len(t)]) # [3. 4. 6.]
5559

5660
Install it with::
5761

RELEASE_NOTES.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,74 @@
22

33
## Changes from 4.4.2 to 4.4.3
44

5-
XXX version-specific blurb XXX
5+
This is a maintenance release focused on faster CTable cold-start, printing
6+
and groupby performance, a lighter `import blosc2`, new raw-storage access
7+
for columns, and support for the new J2K/HTJ2K codec plugins.
8+
9+
### CTable performance
10+
11+
- **Lazy column opening in views**: `select()` (and other view-producing
12+
operations) no longer open every projected column up front. A column is
13+
only opened from storage when the view actually reads it, so selecting and
14+
then touching a subset of columns — or aggregating a single one — skips
15+
the cold-start cost of the rest.
16+
- **Lazy index opening in queries**: query planning no longer opens every
17+
SUMMARY-indexed column on a wide persistent table; only indexes for
18+
columns actually referenced by the predicate are loaded.
19+
- **Faster table printing**: `repr()`/`to_string()` now memoise per-column
20+
sparse gathers for the duration of a render and combine the head and tail
21+
rows into a single sparse read per column. Each column is read from
22+
storage once instead of ~6 times (precision detection, width sizing and
23+
row rendering all hit the cache).
24+
- **Groupby with integral float keys**: float key columns whose values are
25+
integral and fit a compact non-negative range (e.g. float32 id/second
26+
columns) now take the dense single-key fast path instead of the markedly
27+
slower generic float-hash path. Fractional or non-finite keys fall back
28+
automatically.
29+
- **No tempdir in read mode**: opening a `.b2z`/`.b2d` store in `'r'` mode
30+
no longer creates a temporary working directory, since nothing is ever
31+
written.
32+
33+
### Lighter imports and prefetcher rework
34+
35+
- **asyncio dependency dropped**: the on-disk chunk prefetcher used by the
36+
UDF and numexpr fallback engines now uses plain `concurrent.futures`
37+
instead of an asyncio event loop. `import blosc2` no longer pulls in
38+
~30 asyncio modules, saving ~3 MB of memory footprint at import time.
39+
- **Prefetcher deadlock fixed**: an exception during evaluation could leave
40+
the generator finalizer blocked forever in `thread.join()` while the
41+
reader thread was stuck on a full prefetch queue. A stop event now makes
42+
the producer bail out when its consumer goes away.
43+
44+
### New features
45+
46+
- **`Column.raw` accessor**: returns the underlying storage container of a
47+
column (`NDArray`, `ListArray`, `DictionaryColumn`, …) directly. Unlike
48+
`Column.__getitem__`, which always materializes NumPy arrays, this is the
49+
column as a blosc2-native compressed object — usable as a lazy-expression
50+
operand without decompressing, and exposing storage details like `schunk`,
51+
`chunks` or `cparams`. Note that this is a *physical* view: fixed-width
52+
containers are over-allocated to chunk capacity, so slice to `len(table)`
53+
to get just the live rows, and no validity-mask or null-sentinel
54+
processing is applied. Raises `AttributeError` for computed columns,
55+
which have no backing storage.
56+
- **J2K and HTJ2K codec IDs**: `blosc2.Codec.J2K` and `blosc2.Codec.HTJ2K`
57+
expose the IDs for the new JPEG 2000 codec plugins (installable with
58+
`pip install blosc2-j2k` and `pip install blosc2-htj2k`).
59+
60+
### Fixes
61+
62+
- **`--float-trunc-prec` and nested columns**: the precision-truncation
63+
filter of the `parquet_to_blosc2` CLI now propagates to float fields
64+
inside nested (struct/list) columns too.
65+
- **Guard for unsupported computed-column expressions**: expressions that
66+
would serialize to an empty or non-round-trippable string are now rejected
67+
with an early, actionable `ValueError` at `add_computed_column()` time,
68+
instead of silently breaking on reload.
69+
70+
### Build
71+
72+
- **C-Blosc2 updated to 3.1.3.**
673

774
## Changes from 4.4.1 to 4.4.2
875

doc/reference/ctable.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,11 +565,13 @@ Data access
565565
.. autosummary::
566566

567567
Column.view
568+
Column.raw
568569
Column.take
569570
Column.iter_chunks
570571
Column.assign
571572

572573
.. autoproperty:: Column.view
574+
.. autoproperty:: Column.raw
573575
.. automethod:: Column.take
574576
.. automethod:: Column.iter_chunks
575577
.. automethod:: Column.assign

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ dependencies = [
4040
"requests",
4141
"threadpoolctl; platform_machine != 'wasm32'",
4242
]
43-
version = "4.4.3.dev0"
43+
version = "4.4.3"
4444
[project.entry-points."array_api"]
4545
blosc2 = "blosc2"
4646

src/blosc2/ctable.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -819,13 +819,23 @@ def is_ndarray(self) -> bool:
819819
return col is not None and isinstance(col.spec, NDArraySpec)
820820

821821
@property
822-
def array(self):
822+
def raw(self):
823823
"""The underlying storage container for this column, without null-value processing.
824824
825825
Returns the raw :class:`blosc2.NDArray`, :class:`~blosc2.ListArray`,
826-
:class:`~blosc2.DictionaryColumn`, or scalar varlen array directly,
827-
allowing bulk reads that bypass the null-sentinel scan in
828-
:meth:`__getitem__`.
826+
:class:`~blosc2.DictionaryColumn`, or scalar varlen array directly.
827+
Unlike :meth:`__getitem__`, which always materializes NumPy arrays,
828+
this is the column as a blosc2-native compressed object: usable as a
829+
lazy-expression operand without decompressing, and exposing storage
830+
details such as ``schunk``, ``chunks``, ``cparams`` or
831+
``iterchunks_info()``.
832+
833+
This is a physical view of the column: fixed-width containers are
834+
over-allocated to chunk capacity for appends, so their first axis is
835+
longer than ``len(column)`` and positions of rows deleted from the
836+
table still hold their old values. No validity-mask or null-sentinel
837+
processing is applied; use the :class:`Column` interface for logical
838+
reads.
829839
830840
Raises :exc:`AttributeError` for computed (virtual) columns, which have
831841
no backing storage.

src/blosc2/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = "4.4.3.dev0"
1+
__version__ = "4.4.3"
22
__array_api_version__ = "2024.12"

tests/ctable/test_column.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,7 @@ class R:
10421042

10431043

10441044
# -------------------------------------------------------------------
1045-
# Column.array property
1045+
# Column.raw property
10461046
# -------------------------------------------------------------------
10471047

10481048

@@ -1056,40 +1056,43 @@ class ArrayRow:
10561056
)
10571057

10581058

1059-
def test_column_array_ndarray():
1060-
"""array on a numeric column returns the underlying NDArray directly."""
1059+
def test_column_raw_ndarray():
1060+
"""raw on a numeric column returns the underlying NDArray directly."""
10611061
t = CTable(Row, new_data=DATA20)
1062-
raw = t.id.array
1062+
raw = t.id.raw
10631063
assert isinstance(raw, blosc2.NDArray)
10641064
assert raw is t._cols["id"]
1065+
# Physical view: over-allocated to chunk capacity, so slice to live rows.
1066+
assert raw.shape[0] >= len(t)
1067+
np.testing.assert_array_equal(raw[: len(t)], np.arange(20, dtype=np.int64))
10651068

10661069

1067-
def test_column_array_dictionary():
1068-
"""array on a dictionary column returns the underlying DictionaryColumn."""
1070+
def test_column_raw_dictionary():
1071+
"""raw on a dictionary column returns the underlying DictionaryColumn."""
10691072
from blosc2.dictionary_column import DictionaryColumn
10701073

10711074
t = CTable(DictRow, new_data=[("acme", 1.0), ("globex", 2.0), ("acme", 3.0)])
1072-
raw = t.vendor.array
1075+
raw = t.vendor.raw
10731076
assert isinstance(raw, DictionaryColumn)
10741077
assert raw is t._cols["vendor"]
10751078

10761079

1077-
def test_column_array_varlen_scalar():
1078-
"""array on a varlen-string column returns the underlying _ScalarVarLenArray."""
1080+
def test_column_raw_varlen_scalar():
1081+
"""raw on a varlen-string column returns the underlying _ScalarVarLenArray."""
10791082
from blosc2.scalar_array import _ScalarVarLenArray
10801083

10811084
@dataclass
10821085
class VLRow:
10831086
note: str = blosc2.field(blosc2.vlstring())
10841087

10851088
t = CTable(VLRow, new_data=[("hello",), ("world",)])
1086-
raw = t.note.array
1089+
raw = t.note.raw
10871090
assert isinstance(raw, _ScalarVarLenArray)
10881091
assert raw is t._cols["note"]
10891092

10901093

1091-
def test_column_array_list():
1092-
"""array on a list column returns the underlying ListArray."""
1094+
def test_column_raw_list():
1095+
"""raw on a list column returns the underlying ListArray."""
10931096
from blosc2.list_array import ListArray
10941097

10951098
t = CTable(
@@ -1099,28 +1102,28 @@ def test_column_array_list():
10991102
(2.0, "b", "bar", ["z"]),
11001103
],
11011104
)
1102-
raw = t.tags.array
1105+
raw = t.tags.raw
11031106
assert isinstance(raw, ListArray)
11041107
assert raw is t._cols["tags"]
11051108

11061109

1107-
def test_column_array_computed_raises():
1108-
"""array raises AttributeError for computed (virtual) columns."""
1110+
def test_column_raw_computed_raises():
1111+
"""raw raises AttributeError for computed (virtual) columns."""
11091112
t = CTable(Row, new_data=DATA20)
11101113
t.add_computed_column("double_score", lambda cols: cols["score"] * 2)
11111114
with pytest.raises(AttributeError, match="computed"):
1112-
_ = t.double_score.array
1115+
_ = t.double_score.raw
11131116

11141117

1115-
def test_column_array_bypasses_null_scan():
1116-
"""array returns values that include the raw sentinel, not NaN-substituted values."""
1118+
def test_column_raw_bypasses_null_scan():
1119+
"""raw returns values that include the raw sentinel, not NaN-substituted values."""
11171120

11181121
@dataclass
11191122
class NullRow:
11201123
x: float = blosc2.field(blosc2.float64(null_value=-1.0))
11211124

11221125
t = CTable(NullRow, new_data=[(1.0,), (-1.0,), (3.0,)])
1123-
raw = t.x.array[:]
1126+
raw = t.x.raw[:]
11241127
# The raw array contains the sentinel as-is, not converted to NaN.
11251128
assert raw[1] == -1.0
11261129

0 commit comments

Comments
 (0)