NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #2622 most downloaded on PyPI
A fast & compressed ndarray library with a flexible compute engine.
Last release 4 days ago
30 Sep 2026
Release timing varies
gaps range from 9 days to 2 months
Nearly every release is documented
notes for 59 of the last 60 stable releases
2 versions withdrawn
withdrawn after publishing
5 years old
113 releases · first in 2021
One column per quarter.
Python-Blosc2 4.14.1 is a security and feature release introducing safe persisted-object deserialization by default, Caterva2 remote stores and tables
Python-Blosc2 4.14.1 is a security and feature release introducing safe
persisted-object deserialization by default, Caterva2 remote stores and
tables, compressed slicing for RemoteArray, and optimized expression
gather performance.
deserialize="safe". This includesopen(), load(), from_cframe(), CFrame constructors (CFrameNDArray,CFrameCTable), store traversal (TreeStore, DictStore, EmbedStore),deserialize="full" explicitly. Blocked values raise the new publicUnsafeDeserializationError. This default change is intentionallyblosc2.open() now opens Caterva2 groups as RemoteStore and Caterva2URLPath tables as RemoteCTable.RemoteCTable backed by Caterva2 supports on-demand reads, bounded rowselect()), iteration, and materialization.urlpath= writes batches directly to disk and publishes only after theRemoteArray.slice(item=(), **kwargs) to extract a requested selectionNDArray without assembling thecparams are forwarded directlyNDArray.slice.aux_miniexpr during expressionPython-Blosc2 4.14.0 adds remote tables, shared caches, native HDF5 range reads, and indexed queries over remote data. It bundles C-Blosc2 3.3.5.
Python-Blosc2 4.14.0 adds remote tables, shared caches, native HDF5 range reads,
and indexed queries over remote data. It bundles C-Blosc2 3.3.5.
blosc2.open() returns RemoteArray, RemoteCTable, or RemoteStorepath= selects a node inside a container;dataset= remains an alias. Remote Zarr v2/v3 groups now open as stores.RemoteCTable reads B2Z CTables, single-file Parquet, and PyTables tableslazy=False imports Parquet eagerly.max_concurrencyRemoteObject provides the common source, attributes, traffic, cache,RemoteStore, TreeStore, and DictStore.info summaries of their contents without opening leaf readers.cache_dir= retains metadata and accessed data across sessions. Addshared_cache=True for simultaneous processes; all users of that cachesave() writes a reference with retained cache data, without fetching missinginclude_cache=False creates a cold reference. materialize() producescopy(), to_b2z(), and to_b2d() do likewise.TreeStore can persist RemoteStore mounts. Reference exports preserve them;refresh(). Successful refreshRemoteCTable without requiringcontains() and overlaps() provide row predicates; optionalkind="membership" indexes accelerate flat scalar-list queries.batch_rows=2048. Pass None for caller-managedlist_serializer="arrow" or CLI --list-serializer arrow to opt in.asarray() data corruption for arrays larger than 16 MB with blockhdf5_index replaces refs in blosc2.open() and RemoteArray. It acceptshdf5 extra now requires only h5py and hdf5plugin.RemoteCTable.save() now saves a reference and returns its path. Usematerialize() for the former full-copy behavior. Local CTable.save() isoverwrite=True.max_cache_bytes=None for unlimitedlazy=False is rejected.Python-Blosc2 4.14.1 is a security and feature release introducing safe
persisted-object deserialization by default, Caterva2 remote stores and
tables, compressed slicing for RemoteArray, and optimized expression
gather performance.
deserialize="safe". This includes
open(), load(), from_cframe(), CFrame constructors (CFrameNDArray,
CFrameCTable), store traversal (TreeStore, DictStore, EmbedStore),
and CTable variable-length and batch columns. Safe mode rejects embedded
objects, references, remote/source descriptors, proxies, and lazy recipes
before reconstruction.deserialize="full" explicitly. Blocked values raise the new public
UnsafeDeserializationError. This default change is intentionally
behavior-breaking for authority-bearing serialized values.blosc2.open() now opens Caterva2 groups as RemoteStore and Caterva2
URLPath tables as RemoteCTable.RemoteCTable backed by Caterva2 supports on-demand reads, bounded row
slices, column projection (select()), iteration, and materialization.urlpath= writes batches directly to disk and publishes only after the
entire read succeeds.RemoteArray.slice(item=(), **kwargs) to extract a requested selection
directly as an independent compressed NDArray without assembling the
complete selection as an uncompressed NumPy array.cparams are forwarded directly
to NDArray.slice.aux_miniexpr during expression
gather operations (#728, PR #728). Full blocks are overwritten directly by
raw NumPy gather, keeping zero-padding only for partial edge blocks.
Thanks to @Johnny-Kao.A maintenance and performance follow-up to 4.13.0 focused on remote data access and expanded platform support. Lazy access is now the default for remo
A maintenance and performance follow-up to 4.13.0 focused on remote data
access and expanded platform support. Lazy access is now the default for remote
arrays and container datasets, local HDF5 files are read directly via h5py
without auxiliary dependencies, warm opens replay cached bootstrap metadata
to eliminate redundant network discovery, disk caches use human-readable
directory structures, and native Windows ARM64 wheels are now built and tested.
blosc2.open().b2nd files or container datasets (HDF5, Zarr, B2Z) now returns aRemoteArray without requiring an explicit lazy=True. Dataset pathslazy=False with NotImplementedError rather.b2nd), cache_dir= keeps the default lazy access and persists fetched chunksRemoteArray carrier. Pass lazy=False to download the complete containermmap_mode= or a nonzero offset= forces the eager path.h5py. Local .h5/.hdf5 datasets are nowh5py without requiring kerchunk, zarr, orfsspec. Chunked datasets retain their native HDF5 chunk layout, whilerefs= argumentscache_dir= reuse a single on-disk reference snapshot.cache_bytes, respect max_cache_bytes, and can be evicted withtrim_cache().cache_dir=. Persistent disk caches nowhierarchy.b2z--03cc6a2f9314/d0/a1.b2nd) with a 12-character identityRemoteStore caches follow thelazy=False usetemperatures.b2nd--4908c5e5602a/temperatures.b2nd).cache_path= targetswin_arm64) wheel building and testing via GitHub Actions runners,conftest.py, and test deadlock diagnostics.examples/remote/lazy-expr.py.h5, .hdf5, .b2z, .zarr) with a nonzero offsetfile:// HDF5 URLs on Windows. Dataset path separators (::) infile:// URLs survive URL-to-path conversion on Windows platformsfile:///C:/path/x.h5::/d0/a2).RemoteArray.info and str() mask sensitive queryRemoteArray.info now also worksload_tensor() eager access. Explicitly requests eager access so it noRemoteArray intermediate when loadinghdf5plugin) when read via kerchunk. Chunks containing multi-chunkblosc2.from_cframe() now handle the resultingAttributeError on .tobytes().Python-Blosc2 4.14.0 adds remote tables, shared caches, native HDF5 range reads, and indexed queries over remote data. It bundles C-Blosc2 3.3.5.
blosc2.open() returns RemoteArray, RemoteCTable, or RemoteStore
according to the selected object. path= selects a node inside a container;
dataset= remains an alias. Remote Zarr v2/v3 groups now open as stores.RemoteCTable reads B2Z CTables, single-file Parquet, and PyTables tables
on demand. Column projection avoids unrelated data; Parquet reads and caches
individual physical fields by row group. lazy=False imports Parquet eagerly.max_concurrency
and transport-buffer budgets. Decoding and cache publication stay serialized.RemoteObject provides the common source, attributes, traffic, cache,
reference-saving, and lifetime API. RemoteStore, TreeStore, and DictStore
provide .info summaries of their contents without opening leaf readers.cache_dir= retains metadata and accessed data across sessions. Add
shared_cache=True for simultaneous processes; all users of that cache
directory must enable sharing. Persistent caches also support local sources.save() writes a reference with retained cache data, without fetching missing
data. include_cache=False creates a cold reference. materialize() produces
independent local data; table copy(), to_b2z(), and to_b2d() do likewise.TreeStore can persist RemoteStore mounts. Reference exports preserve them;
materialization expands them, preserves group attributes, and rebuilds indexes.refresh(). Successful refresh
invalidates previously obtained child handles; callers must retrieve them again.RemoteCTable without requiring
PyTables installed. Supported persisted PyTables indexes are imported as
Blosc2 OPSI indexes and cached for subsequent queries.contains() and overlaps() provide row predicates; optional
kind="membership" indexes accelerate flat scalar-list queries.batch_rows=2048. Pass None for caller-managed
batching. Existing schemas retain their stored batching behavior.list_serializer="arrow" or CLI --list-serializer arrow to opt in.
Dense local Arrow-backed list exports keep their Arrow buffers where possible.asarray() data corruption for arrays larger than 16 MB with block
partitions that divide chunks but are not C-contiguous (#723, PR #724).
Thanks to @jeandet.hdf5_index replaces refs in blosc2.open() and RemoteArray. It accepts
a native index dictionary or JSON index path; legacy Kerchunk maps are rejected.
The hdf5 extra now requires only h5py and hdf5plugin.RemoteCTable.save() now saves a reference and returns its path. Use
materialize() for the former full-copy behavior. Local CTable.save() is
unchanged. Replacing a reference destination requires overwrite=True.max_cache_bytes=None for unlimited
retention. This does not limit peak RAM or total disk use.lazy=False is rejected.
Ordinary table/store disk caches remain exclusive to one owner at a time.…on disk across sessions. cache_storage is deprecated in favor of cache_dir .
This release turns Python-Blosc2's remote capabilities into a comprehensive,
portable remote data access layer. It introduces RemoteArray as the unified
lazy array interface with bounded in-memory and persistent disk caching,
RemoteStore for discovering and navigating multi-dataset hierarchies across
B2Z, Zarr, and HDF5 containers over fsspec, portable reference exports and
snapshots, the .attrs user metadata interface, interactive remote browsing
in b2view, and the b2nd-to-zarr CLI converter.
Note: The remote cache protocol should be considered somewhat experimental
until it has seen more real S3/HTTP usage; feedback is very welcome!
Unified RemoteArray with bounded caching: blosc2.open(..., lazy=True)
now returns a RemoteArray object that provides a consistent slicing,
fetching, and caching interface across standalone .b2nd files, container
datasets, and Caterva2 endpoints.
CachePolicy.MEMORY): Fetched chunks aremax_cache_bytes (defaults to 256 MiB) withCachePolicy.DISK): Specify cache_dir (orcache_path) to retain fetched chunks and frame layout metadata on diskcache_storage is deprecated in favor of cache_dir.CachePolicy.NONE): Disables chunk retentionfsspec URLs are partitioned bystorage_options so distinct credentials or custom endpoints do not clash.fetch() and afetch() preheat specifiedmaterialize(item) returns an independent in-memory NDArray.RemoteStore hierarchy discovery and shared caching: Multi-dataset
containers (.b2z, .zarr, and .h5/.hdf5) can now be explored and
sliced lazily without downloading the entire container:
store.keys() or iteration (for name in store:). Access nodes using slashstore["group/dataset"]) or chained indexingstore["group"]["dataset"]).store.get_info(name) returns a RemoteNodepath, kind, attrs, diagnostic) without initializing leaf readersRemoteStore sharecache_dir). When the budget is reached, cross-leaf LRU eviction freesstore.refresh() atomically checks and reloads remoteRuntimeErrorPortable references and snapshots:
RemoteArray.save("ref.b2nd") exports portable array descriptors withRemoteStore.save("snapshot.b2z") exports an entire remote hierarchy.b2zinclude_cache=True (default) bundles warm cached chunks for offline orinclude_cache=False exports lightweight streamingmutable=False (default) produces immutable snapshots safe on read-onlymutable=True allows local cache expansion upon reopening.blosc2.open()blosc2.open("snapshot.b2z") returns a RemoteStore).Remote container adapters (B2ZNDSource, ZarrNDSource, HDF5NDSource):
B2ZNDSource: Direct byte-range access to external uncompressed ZIP_STORED.b2z archives without downloading or unzipping.ZarrNDSource: Reads remote Zarr v2 and v3 arrays lazily on demand, cachingHDF5NDSource: Accesses remote HDF5 datasets lazily via kerchunk referencefile.h5/group/data), doublefile.h5::group/data), and dataset="group/data". Manifests areNew b2nd-to-zarr CLI utility: Converts local Blosc2 NDArray containers
into Zarr v2 (with numcodecs blosc/blosc2 compressor) or Zarr v3 datasets.
Recommended .attrs metadata interface: Arrays, containers, and proxy
sources now expose .attrs as the recommended interface for user-defined
metadata (aliased to .vlmeta). C2Array.attrs selects server user attributes
while C2Array.vlmeta retains Caterva2 protocol metadata. Internal HDF5
dimension metadata is filtered out of user .attrs. .vlmeta remains fully
supported and is not deprecated.
Interactive remote browsing in b2view:
.b2z, .zarr, .h5, fsspec URLs, S3, HTTPS).--cache-dir option to persist discovery metadata and fetched chunksEnhanced LazyExpr AST shape inferencer:
arr[10:20, ...]).slice and len builtins, common array methods (astype,copy, flatten, ravel, squeeze), and attributes (shape, size,ndim, itemsize).Bundled C-Blosc2 3.3.4: Updated to the latest C-Blosc2 release.
Test suite and CI reliability: Added pytest watchdog deadman timer
(PYTEST_DEADMAN_SECONDS, PYTEST_DEADMAN_SESSION_SECONDS) and thread stack
dumps to catch and diagnose worker hangs. Fixed file descriptor exhaustion by
clearing index handle caches between tests, and resolved Windows-specific
in-process HTTP server aborts and file locking races.
A maintenance and performance follow-up to 4.13.0 focused on remote data
access and expanded platform support. Lazy access is now the default for remote
arrays and container datasets, local HDF5 files are read directly via h5py
without auxiliary dependencies, warm opens replay cached bootstrap metadata
to eliminate redundant network discovery, disk caches use human-readable
directory structures, and native Windows ARM64 wheels are now built and tested.
blosc2.open()
on remote .b2nd files or container datasets (HDF5, Zarr, B2Z) now returns a
lazy RemoteArray without requiring an explicit lazy=True. Dataset paths
require lazy access and reject lazy=False with NotImplementedError rather
than attempting a silent eager download. For standalone array files (such as
.b2nd), cache_dir= keeps the default lazy access and persists fetched chunks
in a RemoteArray carrier. Pass lazy=False to download the complete container
there instead. mmap_mode= or a nonzero offset= forces the eager path.h5py. Local .h5/.hdf5 datasets are now
read directly through h5py without requiring kerchunk, zarr, or
fsspec. Chunked datasets retain their native HDF5 chunk layout, while
contiguous datasets receive automatically chosen Blosc2 cache chunks. Dataset
attributes are loaded from HDF5 metadata, and file handles are safely closed
when the source object is garbage-collected. Explicit refs= arguments
continue to select the kerchunk reference reader.cache_dir= reuse a single on-disk reference snapshot.cache_bytes, respect max_cache_bytes, and can be evicted with
trim_cache().cache_dir=. Persistent disk caches now
mirror the source filename and internal dataset hierarchy (e.g.,
hierarchy.b2z--03cc6a2f9314/d0/a1.b2nd) with a 12-character identity
fingerprint computed from the URL and storage options. Unsafe characters are
escaped and long components are shortened. RemoteStore caches follow the
same naming convention. Complete containers localized with lazy=False use
it too (e.g., temperatures.b2nd--4908c5e5602a/temperatures.b2nd).cache_path= targets
and direct opens of existing carrier files continue to work. Legacy cache
directories can be safely deleted to reclaim disk space.win_arm64) wheel building and testing via GitHub Actions runners,
including build configuration adjustments for Clang and runtime fallback for
JIT on ARM64.conftest.py, and test deadlock diagnostics.examples/remote/lazy-expr.py
and documentation illustrating how to evaluate lazy expressions across
remote B2Z, Zarr, and HDF5 arrays..h5, .hdf5, .b2z, .zarr) with a nonzero offset
now opens the embedded Blosc2 frame directly instead of attempting container
parsing.file:// HDF5 URLs on Windows. Dataset path separators (::) in
file:// URLs survive URL-to-path conversion on Windows platforms
(e.g., file:///C:/path/x.h5::/d0/a2).RemoteArray.info and str() mask sensitive query
parameters and credentials in signed URLs. RemoteArray.info now also works
reliably for local B2Z sources without raising an error.load_tensor() eager access. Explicitly requests eager access so it no
longer inadvertently returns a lazy RemoteArray intermediate when loading
known remote paths.hdf5plugin) when read via kerchunk. Chunks containing multi-chunk
super-chunk frames decoded via blosc2.from_cframe() now handle the resulting
bytes correctly, avoiding an AttributeError on .tobytes().This release focuses on efficient remote arrays. Blosc2 containers can now be read and written through fsspec URLs, while lazy proxies fetch only the
This release focuses on efficient remote arrays. Blosc2 containers can now be
read and written through fsspec URLs, while lazy proxies fetch only the required
blocks, overlap requests and reuse validated caches. Caterva2 arrays gain
block-range reads and concurrent chunk writers. UTF-8 FULL-index lookups also
use substantially less memory by bisecting their vocabulary on disk.
New blosc2[fsspec] extra: blosc2.open(), save_array() and save_tensor()
accept any fsspec URL — s3://,
gs://, https://, zip:// and chained URLs. open() can download the
container, keep a validated local copy with cache_storage=, or fetch only
requested slices with lazy=True and the new blosc2.FsspecNDSource; lazy
reads can also use a persistent cache. Saves upload one complete object, but
URL-backed mutable containers are not supported. Protocol drivers and
credentials remain the caller's responsibility.
A C2Array can be written to a chunk at a time, which is how several
processes fill one remote array at once: update_chunk() (and its async
aupdate_chunk()) posts one compressed chunk into a slot of a pre-sized
array, and written_chunks() says which slots hold anything yet. The array is
laid out with blosc2.uninit() and uploaded -- a couple of hundred bytes
whatever its size -- and each slot is written once: a second write raises
blosc2.ChunkAlreadyWritten, which is the whole of the coordination between
writers. Writing into an empty slot appends to the frame and moves no other
chunk, so a fill is cheap and a concurrent reader's cached offsets stay good.
Needs a Caterva2 subscriber that serves the endpoint.
C2Array.stamp, which is what a Proxy checks its cache against, now names
which array it is as well as whether it has changed. A subscriber writes a
nonce into a filled array's vlmeta, so a cache is no longer served against a
different array that came to sit at the same path with the same size and
mtime; and a complete array — every chunk written, so every further write
refused — is stamped without its mtime, so a cache of it survives a republish
or a copy instead of being thrown away. Arrays that were never filled a chunk
at a time are stamped exactly as before.
Proxy.fetch() takes a max_concurrency= argument, and reads it from the
source when the source has one, so blosc2.open(url, lazy=True, max_concurrency=...)
overlaps its chunk fetches in a thread pool. Ordinary
slicing benefits, not just the async afetch(). A lazy fsspec proxy defaults
to 8, matching what afetch() already used for remote sources; pass 1 for a
protocol with no latency to hide, where the pool costs ~10 µs per chunk and
saves nothing. Other sources stay serial unless asked, since this is only safe
for a thread-safe get_chunk.
blosc2.open(url, lazy=True) fetches blocks rather than whole chunks when
a slice lands in a small part of a large one. With the partitions
blosc2.asarray() picks by default a chunk holds a hundred-odd blocks, so a
point or window read costs about 1% of what it used to: measured against S3,
5-17x faster on arrays with multi-megabyte chunks and 2-5x on 1 MB ones. It is
never a loss, because the two thresholds that decide it — a chunk under a
megabyte is one cheap request anyway, and wanting more than half of a chunk's
blocks is wanting the chunk — need nothing read to answer. Peak memory drops
with the traffic, since a fetch in flight is now a block rather than a chunk.
bench/ndarray/fsspec-block-granularity.py measures both on any array.
A Proxy over a C2Array now reads only the required compressed blocks from
file-backed datasets using concurrent, batched HTTP byte ranges. On
cat2.cloud's kevlar-tomo.b2nd, this reduced a corner slice from 2.723 MB to
0.031 MB and a ten-chunk slice from 1.01 s to 0.14 s. Computed datasets fall
back to whole-chunk reads. blosc2.ByteRangeNDSource provides the same frame
reader to FsspecNDSource, C2NDSource and custom transports, with lazy,
persistent caching of frame layout metadata.
DictStore.member_window(key) says where a leaf's frame lies inside a .b2z,
as (offset, nbytes). A zip store keeps each external leaf uncompressed, so
those bytes are the frame that leaf would have been written as on its own --
which lets a reader take the window instead of the leaf: Caterva2 now serves a
container leaf from it, so a Proxy over @public/tree.b2z/leaf reads blocks
exactly as it does over a .b2nd (2.8x on a point read, 20x fewer bytes).
None where there is no window: a directory-backed store, an embedded leaf, a
C2Array reference, and a member some other tool repacked compressed --
which is now also left out of the store's keys rather than read as the deflate
output it is, and said plainly when it is the store's own super-chunk.
blosc2.Proxy(src, urlpath=..., mode="a") now adopts the cache left by an
earlier run, including partially fetched chunks. It validates the cache's
shape, dtype and source stamp, refetching stale data if the remote array was
replaced and raising for incompatible caches. Caches from pre-release 4.12.0
builds require one fresh open with mode="w".
The source protocol moved to its own module, blosc2.proxy_source:
ProxySource, ProxyNDSource, ByteRangeNDSource, FsspecNDSource and the
frame reading behind them. They are still blosc2.X and still reachable as
blosc2.proxy.X, so nothing outside need change; what moved for good are the
block-granularity knobs, blosc2.proxy_source.BLOCK_MIN_CBYTES and its
neighbours. This is what lets proxy.py be imported after schunk and
indexing, rather than being dragged in ahead of them by every module that
wants a source.
Querying a utf8() column through its FULL index no longer materializes the
index vocabulary. The query literal is turned into an alphabetical rank by
bisecting the vocabulary sidecar instead, so a lookup reads a few blocks
rather than one fixed-width entry per distinct value. On a 1 Mrow column of
near-unique free text, the first lookup goes from ~62 ms and 739 MiB of peak
memory to ~12 ms and 5.5 MiB.
This release turns Python-Blosc2's remote capabilities into a comprehensive,
portable remote data access layer. It introduces RemoteArray as the unified
lazy array interface with bounded in-memory and persistent disk caching,
RemoteStore for discovering and navigating multi-dataset hierarchies across
B2Z, Zarr, and HDF5 containers over fsspec, portable reference exports and
snapshots, the .attrs user metadata interface, interactive remote browsing
in b2view, and the b2nd-to-zarr CLI converter.
Note: The remote cache protocol should be considered somewhat experimental until it has seen more real S3/HTTP usage; feedback is very welcome!
Unified RemoteArray with bounded caching: blosc2.open(..., lazy=True)
now returns a RemoteArray object that provides a consistent slicing,
fetching, and caching interface across standalone .b2nd files, container
datasets, and Caterva2 endpoints.
CachePolicy.MEMORY): Fetched chunks are
retained in RAM bounded by max_cache_bytes (defaults to 256 MiB) with
automatic LRU eviction after operations.CachePolicy.DISK): Specify cache_dir (or
cache_path) to retain fetched chunks and frame layout metadata on disk
across sessions. cache_storage is deprecated in favor of cache_dir.CachePolicy.NONE): Disables chunk retention
entirely for memory-constrained streaming reads.fsspec URLs are partitioned by
storage_options so distinct credentials or custom endpoints do not clash.fetch() and afetch() preheat specified
slices synchronously or asynchronously into the cache.materialize(item) returns an independent in-memory NDArray.RemoteStore hierarchy discovery and shared caching: Multi-dataset
containers (.b2z, .zarr, and .h5/.hdf5) can now be explored and
sliced lazily without downloading the entire container:
store.keys() or iteration (for name in store:). Access nodes using slash
paths (store["group/dataset"]) or chained indexing
(store["group"]["dataset"]).store.get_info(name) returns a RemoteNode
(path, kind, attrs, diagnostic) without initializing leaf readers
or allocating cache memory. Unsupported nodes degrade gracefully.RemoteStore share
a single cache coordinator (256 MiB RAM by default, or persistent disk
via cache_dir). When the budget is reached, cross-leaf LRU eviction frees
chunks across any leaf in the store. Closing a leaf handle preserves its
warm cache in the active store session.store.refresh() atomically checks and reloads remote
discovery, safely invalidating previously opened leaf handles (RuntimeError
on reuse) to prevent reading inconsistent state.Portable references and snapshots:
RemoteArray.save("ref.b2nd") exports portable array descriptors with
optional persistent caches.RemoteStore.save("snapshot.b2z") exports an entire remote hierarchy
(discovered keys, attributes, and source locators) into a portable .b2z
archive without exposing credentials or secrets.include_cache=True (default) bundles warm cached chunks for offline or
zero-traffic reuse; include_cache=False exports lightweight streaming
references.mutable=False (default) produces immutable snapshots safe on read-only
media; mutable=True allows local cache expansion upon reopening.blosc2.open()
(blosc2.open("snapshot.b2z") returns a RemoteStore).Remote container adapters (B2ZNDSource, ZarrNDSource, HDF5NDSource):
B2ZNDSource: Direct byte-range access to external uncompressed ZIP_STORED
NDArray members within remote .b2z archives without downloading or unzipping.ZarrNDSource: Reads remote Zarr v2 and v3 arrays lazily on demand, caching
converted Blosc2 chunks. Supports scalar and empty arrays, as well as fixed-size
dtypes.HDF5NDSource: Accesses remote HDF5 datasets lazily via kerchunk reference
indexing. Unifies dataset syntax across slashes (file.h5/group/data), double
colons (file.h5::group/data), and dataset="group/data". Manifests are
indexed once per container and shared across leaves.New b2nd-to-zarr CLI utility: Converts local Blosc2 NDArray containers
into Zarr v2 (with numcodecs blosc/blosc2 compressor) or Zarr v3 datasets.
Recommended .attrs metadata interface: Arrays, containers, and proxy
sources now expose .attrs as the recommended interface for user-defined
metadata (aliased to .vlmeta). C2Array.attrs selects server user attributes
while C2Array.vlmeta retains Caterva2 protocol metadata. Internal HDF5
dimension metadata is filtered out of user .attrs. .vlmeta remains fully
supported and is not deprecated.
Interactive remote browsing in b2view:
.b2z, .zarr, .h5, fsspec URLs, S3, HTTPS).--cache-dir option to persist discovery metadata and fetched chunks
between viewer sessions.Enhanced LazyExpr AST shape inferencer:
arr[10:20, ...]).slice and len builtins, common array methods (astype,
copy, flatten, ravel, squeeze), and attributes (shape, size,
ndim, itemsize).Bundled C-Blosc2 3.3.4: Updated to the latest C-Blosc2 release.
Test suite and CI reliability: Added pytest watchdog deadman timer
(PYTEST_DEADMAN_SECONDS, PYTEST_DEADMAN_SESSION_SECONDS) and thread stack
dumps to catch and diagnose worker hangs. Fixed file descriptor exhaustion by
clearing index handle caches between tests, and resolved Windows-specific
in-process HTTP server aborts and file locking races.
Nullability in CTable is rebuilt on Arrow's own model. A nullable column now keeps its nulls in a per-column validity sidecar instead of reserving a v
Nullability in CTable is rebuilt on Arrow's own model. A nullable column now
keeps its nulls in a per-column validity sidecar instead of reserving a value
from its own range, which is what makes it lossless — an int8 column can hold
-128, a utf8 one can hold "", and a float64 one can tell NaN from
missing. That is the new default for columns created from now on; nothing on
disk changes, and sentinel storage remains supported indefinitely, one keyword
away. Built on top of it: predicates follow three-valued (Kleene) logic, so
~(t.price > 10) no longer returns the null rows, and column indexes are
null-aware, which makes min/max over a nullable column answer from the
index instead of scanning.
On the packaging side, wheels are now a single Stable ABI (abi3) build per
platform covering CPython 3.11+, with free-threaded 3.14 and 3.15 shipping
alongside.
nullable=True resolves to, and what every nullable columnblosc2.bool(nullable=True) no255 and keeps np.bool_, blosc2.int8(nullable=True) hasblosc2.utf8(nullable=True) accepts any string"" and "\x00", and blosc2.complex128(nullable=True) isint8 could not hold -128, a free-text utf8to_arrow(from_arrow(x)) now returns x forbool, full-range int8/uint8, float64 containingnan/±inf/-0.0 as values, utf8 containing "" and"__BLOSC2_NULL__", and timestamp with int64.min as a value — none ofNone is how you write a null under mask storage (t.append((None,)),t["price"][3] = None), which a fixed-width sentinel column cannot accept atis_null() is unchanged and remains the uniform API across every kind.255 stays permanently in place.null_storage="sentinel", or any explicit null_value=) or globallyNullPolicy. Setting a type-wide NullPolicy sentinel field stillNullPolicy(float_value=...) code is unaffected — with one unavoidable255 is the only value a nullable bool may reserve, so it is alsobool_value's default, and NullPolicy(bool_value=255) carries nonull_storage or column_null_values.ValueError: Unsupported schema version 3 — the hint namingconvert_nulls(to='sentinel') ships in 4.11.0, so only readers that canblosc2.null_policy(blosc2.NullPolicy(null_storage="sentinel")). ThatNaNColumn.null_storage reports where a column keeps its nulls and infoint64 nullable[mask]), so CTable.convert_nulls() canNaN is a value,min/max are taken over the rows that carry a value. AINT64_MIN sentinel is exactly as invisible to a summary as aColumn.min/Column.max answer from the index for a nullable columnint64, measured), where before every nullable columnwhere() with an OR over a nullable indexed column uses the indexrebuild_index()int64 column), because the&, |, ^ and ~ combine it by Kleene's rules instead ofFalse at the leaf. t.where(t.price > 10) gives the rowst.where(~(t.price > 10)) the rows definitely notwhere() keeps what a predicate is true for, so the rows it returns for~(t.price > 10) used to invert a null that had already beenFalse and so returned every null row — the exact opposite of~((a > 10) & (b == 999)) dropped rows that qualify,unknown & false is false, not unknown, and only a real thirdp.is_null() gives the rows it cannot answer for, p.null_count()t.where(p.fillna(True)) keeps what cannot be ruled out.fillna(False) is the other reading, and is what where() appliesblosc2.LazyExpr, sot.where(dict_col != "x") no longerdict_col is null (its reserved code differs fromdict_col == None remains how to askColumn.isin() stays deliberately two-valued — it returns aNone among thecp311-abi3 wheelpip install blosc2; what changes iscp314t andcp315t wheels — the free-threaded stable ABI (abi3t, PEP 803) starts atencoding=. from_csv() defaults toutf-8-sig (plain UTF-8, absorbing a byte-order mark if present) andto_csv() to utf-8, but either can be given another codec, which is whatutf8 versus fixed-width string columns, when a dictionary column pays@jit decorator actually buys you (with a workedasarray() corrupted arrays whose chunks overhang the shape. Above 16 MBSChunk.update_data(), and its guard(146, 23802) with chunks (147, 23802)) passedgroup_by returned the wrong min for a bool value column. TheboolTrue group reduced to False. Reachable with any plainsort_by on a bool column raised, and on a signed-integer-128 for int8) that row sorted as if itbool has no unary minus and a narrow signed type wraps.add_column() after copy() backfilled one row short, and raised for at.where("a > 10") compared the stored sentinel, so any sentinel satisfyingnull_value=999 against > 10) came back as a match. Thet.where(t.a > 10)) was always correct. Fixed for both storages.uint8 ndarray column came back as bool. The bool → uint8uint8 was truncated to~ on a nullable bool column selected its nulls. SQL WHERE semanticsFalse fill instead. (The sentinel path was already== 0 rewrite.)to_csv comparedfrom_csv had nothing to put in an empty field and raised.sum() on a sorted view could return NaN from a column whoseNaN, and unique() could report the fill as data whileconvert_nulls() flattened a nested column, even when it had nothing tosort_by mis-ordered the widest integers. The key was builtint64's minimum sorted as ifuint64 above 2**63 wrapped negative and sortedUnicodeEncodeError on Windows. Both directions are UTF-8 now, and readingcol[key] = value. Adatetime was never encoded to the stored int64, so every key form failed;extend() was unaffected. ISO strings and datetime64 are accepted too.col[0:2] = 7 — and the same write through a boolean mask or an index list —TypeError: iteration over a 0-d array, because the write pathcol[0:2] = None now makes every selectedextend() from another table lost nulls between storages. Copying rowsNone. t[i],repr surfaced the placeholder that occupies a null slot, whichvlstring column inNone. Sentinel columns still show theirto_numpy(masked=True) and dropna() raised on a nullable dictionarynan+0j rather than asndarray of bool forgot it had been widened when reopened, souint8, and the guard against aconvert_nulls(to="sentinel") refused over a value in a deleted row. Thecompact() —This release focuses on efficient remote arrays. Blosc2 containers can now be read and written through fsspec URLs, while lazy proxies fetch only the required blocks, overlap requests and reuse validated caches. Caterva2 arrays gain block-range reads and concurrent chunk writers. UTF-8 FULL-index lookups also use substantially less memory by bisecting their vocabulary on disk.
New blosc2[fsspec] extra: blosc2.open(), save_array() and save_tensor()
accept any fsspec URL — s3://,
gs://, https://, zip:// and chained URLs. open() can download the
container, keep a validated local copy with cache_storage=, or fetch only
requested slices with lazy=True and the new blosc2.FsspecNDSource; lazy
reads can also use a persistent cache. Saves upload one complete object, but
URL-backed mutable containers are not supported. Protocol drivers and
credentials remain the caller's responsibility.
A C2Array can be written to a chunk at a time, which is how several
processes fill one remote array at once: update_chunk() (and its async
aupdate_chunk()) posts one compressed chunk into a slot of a pre-sized
array, and written_chunks() says which slots hold anything yet. The array is
laid out with blosc2.uninit() and uploaded -- a couple of hundred bytes
whatever its size -- and each slot is written once: a second write raises
blosc2.ChunkAlreadyWritten, which is the whole of the coordination between
writers. Writing into an empty slot appends to the frame and moves no other
chunk, so a fill is cheap and a concurrent reader's cached offsets stay good.
Needs a Caterva2 subscriber that serves the endpoint.
C2Array.stamp, which is what a Proxy checks its cache against, now names
which array it is as well as whether it has changed. A subscriber writes a
nonce into a filled array's vlmeta, so a cache is no longer served against a
different array that came to sit at the same path with the same size and
mtime; and a complete array — every chunk written, so every further write
refused — is stamped without its mtime, so a cache of it survives a republish
or a copy instead of being thrown away. Arrays that were never filled a chunk
at a time are stamped exactly as before.
Proxy.fetch() takes a max_concurrency= argument, and reads it from the
source when the source has one, so blosc2.open(url, lazy=True, max_concurrency=...) overlaps its chunk fetches in a thread pool. Ordinary
slicing benefits, not just the async afetch(). A lazy fsspec proxy defaults
to 8, matching what afetch() already used for remote sources; pass 1 for a
protocol with no latency to hide, where the pool costs ~10 µs per chunk and
saves nothing. Other sources stay serial unless asked, since this is only safe
for a thread-safe get_chunk.
blosc2.open(url, lazy=True) fetches blocks rather than whole chunks when
a slice lands in a small part of a large one. With the partitions
blosc2.asarray() picks by default a chunk holds a hundred-odd blocks, so a
point or window read costs about 1% of what it used to: measured against S3,
5-17x faster on arrays with multi-megabyte chunks and 2-5x on 1 MB ones. It is
never a loss, because the two thresholds that decide it — a chunk under a
megabyte is one cheap request anyway, and wanting more than half of a chunk's
blocks is wanting the chunk — need nothing read to answer. Peak memory drops
with the traffic, since a fetch in flight is now a block rather than a chunk.
bench/ndarray/fsspec-block-granularity.py measures both on any array.
A Proxy over a C2Array now reads only the required compressed blocks from
file-backed datasets using concurrent, batched HTTP byte ranges. On
cat2.cloud's kevlar-tomo.b2nd, this reduced a corner slice from 2.723 MB to
0.031 MB and a ten-chunk slice from 1.01 s to 0.14 s. Computed datasets fall
back to whole-chunk reads. blosc2.ByteRangeNDSource provides the same frame
reader to FsspecNDSource, C2NDSource and custom transports, with lazy,
persistent caching of frame layout metadata.
DictStore.member_window(key) says where a leaf's frame lies inside a .b2z,
as (offset, nbytes). A zip store keeps each external leaf uncompressed, so
those bytes are the frame that leaf would have been written as on its own --
which lets a reader take the window instead of the leaf: Caterva2 now serves a
container leaf from it, so a Proxy over @public/tree.b2z/leaf reads blocks
exactly as it does over a .b2nd (2.8x on a point read, 20x fewer bytes).
None where there is no window: a directory-backed store, an embedded leaf, a
C2Array reference, and a member some other tool repacked compressed --
which is now also left out of the store's keys rather than read as the deflate
output it is, and said plainly when it is the store's own super-chunk.
blosc2.Proxy(src, urlpath=..., mode="a") now adopts the cache left by an
earlier run, including partially fetched chunks. It validates the cache's
shape, dtype and source stamp, refetching stale data if the remote array was
replaced and raising for incompatible caches. Caches from pre-release 4.12.0
builds require one fresh open with mode="w".
The source protocol moved to its own module, blosc2.proxy_source:
ProxySource, ProxyNDSource, ByteRangeNDSource, FsspecNDSource and the
frame reading behind them. They are still blosc2.X and still reachable as
blosc2.proxy.X, so nothing outside need change; what moved for good are the
block-granularity knobs, blosc2.proxy_source.BLOCK_MIN_CBYTES and its
neighbours. This is what lets proxy.py be imported after schunk and
indexing, rather than being dragged in ahead of them by every module that
wants a source.
Querying a utf8() column through its FULL index no longer materializes the
index vocabulary. The query literal is turned into an alphabetical rank by
bisecting the vocabulary sidecar instead, so a lookup reads a few blocks
rather than one fixed-width entry per distinct value. On a 1 Mrow column of
near-unique free text, the first lookup goes from ~62 ms and 739 MiB of peak
memory to ~12 ms and 5.5 MiB.
A correctness release: lazy indexing and reductions now follow NumPy in a batch of cases where they quietly did not, the stores close several cross-pr
A correctness release: lazy indexing and reductions now follow NumPy in a
batch of cases where they quietly did not, the stores close several
cross-process read races, and wheels finally ship usable C-Blosc2 development
files. Bundled C-Blosc2 moves to 3.3.2.
expr[0] dropped every length-1 axis of the result, including ones the(1, 4) expression indexed at [0] came back as (4,)(4,) only for the consumed axis and keeps the rest.where() results, whose length is data-dependent, are left alone.LazyUDF and broadcast operands mis-indexed on None. A None in theexpr[None, 2] read the wrong operand region. Closes #403, #688.(a + b).sum() [key] evaluated the reduction over everything and then indexed; thet1 < t2 on datetime64/timedelta64 died with unknown type datetime64[s] and the error was re-raised rather than letting the NumPyNaT decline that route andNDArray.nbytes reported the padded size. It now returns the logicalsize * itemsize, matching NumPy, whenever the shape does not fill thecratio still measures the stored (padded) data, sonbytes / cbytes need not equal cratio; use .schunk.nbytes for theCTable.where() applied a short boolean mask to the wrong rows. A maskEmbedStore and DictStore. Both resolvedEmbedStore.__getitem__DictStore.__getitem__ opened anKeyError, or RuntimeError: Error while getting the buffer). TheDictStore leaf is now atomic. __setitem____getitem__ returns holds no file descriptor — the C layer re-opens theos.replace(), so every such re-open sees one.tmpTreeStore.close() swallowed inline handle failures. A CTable thatclose() said it succeeded. Everyunpack_tensor() turned padding into a phantom column. np.dtype()'' -> f2),CMAKE_INSTALL_PREFIX; the absolute ones made C-Blosc2 generateblosc2.pc and exported targets pointing into the build tempdir, sopkg-config and find_package(Blosc2) both failed against an installedpkg-config, Blosc2::blosc2_shared and Blosc2::blosc2_static..dist-info/licenses, where PEPlibblosc2 instead of three, ~1.5 MBVERSION and SOVERSION, and scikit-build-core-n auto --dist loadfile inpytest.ini), 130s -> 35s locally, falling back to a serial run whenheavy tests, 58% of everything collected andselect rather than extend-select,Nullability in CTable is rebuilt on Arrow's own model. A nullable column now
keeps its nulls in a per-column validity sidecar instead of reserving a value
from its own range, which is what makes it lossless — an int8 column can hold
-128, a utf8 one can hold "", and a float64 one can tell NaN from
missing. That is the new default for columns created from now on; nothing on
disk changes, and sentinel storage remains supported indefinitely, one keyword
away. Built on top of it: predicates follow three-valued (Kleene) logic, so
~(t.price > 10) no longer returns the null rows, and column indexes are
null-aware, which makes min/max over a nullable column answer from the
index instead of scanning.
On the packaging side, wheels are now a single Stable ABI (abi3) build per platform covering CPython 3.11+, with free-threaded 3.14 and 3.15 shipping alongside.
nullable=True resolves to, and what every nullable column
inferred from Arrow, Parquet or CSV gets: blosc2.bool(nullable=True) no
longer reserves 255 and keeps np.bool_, blosc2.int8(nullable=True) has
all 256 values usable, blosc2.utf8(nullable=True) accepts any string
including "" and "\x00", and blosc2.complex128(nullable=True) is
nullable at all for the first time.int8 could not hold -128, a free-text utf8
column had no safe sentinel at all, and Arrow columns whose type had no value
to spare could not be imported. to_arrow(from_arrow(x)) now returns x for
nullable bool, full-range int8/uint8, float64 containing
nan/±inf/-0.0 as values, utf8 containing "" and
"__BLOSC2_NULL__", and timestamp with int64.min as a value — none of
which round-trip through a sentinel.None is how you write a null under mask storage (t.append((None,)),
t["price"][3] = None), which a fixed-width sentinel column cannot accept at
all. is_null() is unchanged and remains the uniform API across every kind.255 stays permanently in place.null_storage="sentinel", or any explicit null_value=) or globally
through NullPolicy. Setting a type-wide NullPolicy sentinel field still
implies sentinel storage for the kinds it covers, so existing
NullPolicy(float_value=...) code is unaffected — with one unavoidable
exception: 255 is the only value a nullable bool may reserve, so it is also
bool_value's default, and NullPolicy(bool_value=255) carries no
information to act on. A bool column that wants a sentinel has to say so with
null_storage or column_null_values.ValueError: Unsupported schema version 3 — the hint naming
convert_nulls(to='sentinel') ships in 4.11.0, so only readers that can
already open the file will print it.blosc2.null_policy(blosc2.NullPolicy(null_storage="sentinel")). That
reinstates the sentinel's lossiness (a float column's nulls become NaN
again, and a type with no value to spare still cannot be imported), which is
the trade being made.Column.null_storage reports where a column keeps its nulls and info
tags each column (int64 nullable[mask]), so CTable.convert_nulls() can
move columns between the two in either direction — never implicitly, and
refusing rather than silently relabelling data when a sentinel is
unavailable.NaN is a value,
following Arrow, and only the sidecar marks a null. Sentinel float columns
keep NaN-as-null. See "Where nulls are stored" in the CTable reference.min/max are taken over the rows that carry a value. A
column's nulls are read from its validity channel and left out, and a segment
with no value at all is flagged rather than summarised. This applies to both
storages — an INT64_MIN sentinel is exactly as invisible to a summary as a
mask column's fill.Column.min/Column.max answer from the index for a nullable column
(236x on a 20M-row int64, measured), where before every nullable column
but a NaN-sentinel float had to scan.where() with an OR over a nullable indexed column uses the index
instead of falling back to a full scan (1.6x on a 20M-row two-column
probe). The fallback existed because the only null filtering available was
global, and a global filter drops a row that is null in one branch but
matches the other; the segment path never needed it, because it evaluates
the predicate, which has been null-aware per leaf since the string-predicate
fix below.rebuild_index()
promotes them.int64 column), because the
incremental per-block summaries folded during writes carry no validity; a
nullable column with no nulls keeps that fast path untouched.&, |, ^ and ~ combine it by Kleene's rules instead of
collapsing it to False at the leaf. t.where(t.price > 10) gives the rows
definitely above 10, t.where(~(t.price > 10)) the rows definitely not
above 10 — nulls in neither.where() keeps what a predicate is true for, so the rows it returns for
a plain comparison are unchanged. What this fixes is everything built on top
of one: ~(t.price > 10) used to invert a null that had already been
collapsed to False and so returned every null row — the exact opposite of
the intent — and ~((a > 10) & (b == 999)) dropped rows that qualify,
because unknown & false is false, not unknown, and only a real third
value can express that. Both query forms are covered and both now agree with
SQL: the string form carries the second channel through an AST rewrite under
negation.p.is_null() gives the rows it cannot answer for, p.null_count()
counts them, and t.where(p.fillna(True)) keeps what cannot be ruled out.
fillna(False) is the other reading, and is what where() applies
implicitly.blosc2.LazyExpr, so
it computes, indexes and plans exactly as before. Measured cost of the exact
answer: a negated two-column conjunction over a 20M-row nullable table runs
1.15x slower than the wrong answer it replaces; every other predicate shape
is unchanged.t.where(dict_col != "x") no longer
returns the rows where dict_col is null (its reserved code differs from
every value's, so it used to match); dict_col == None remains how to ask
for them. And Column.isin() stays deliberately two-valued — it returns a
materialized array and has its own spelling for nulls (None among the
values).cp311-abi3 wheel
that serves CPython 3.11 and every later version, including ones released
after this one. Nothing changes for pip install blosc2; what changes is
that a new CPython no longer has to wait for a blosc2 release to be
installable from a wheel. CI installs that one wheel on 3.11 through 3.15 and
runs a slice of the suite on each, since cibuildwheel only tests a wheel on
the interpreter that built it.cp314t and
cp315t wheels — the free-threaded stable ABI (abi3t, PEP 803) starts at
3.15 and Cython cannot emit it yet. The limited API costs nothing measurable
here: across the Linux and Windows cells of a throwaway benchmark matrix the
worst ratio over every benchmark was 1.075x and 1.054x respectively,
including the call-heavy ones where an ABI cost would show up first.encoding=. from_csv() defaults to
utf-8-sig (plain UTF-8, absorbing a byte-order mark if present) and
to_csv() to utf-8, but either can be given another codec, which is what
an existing file written in a platform codec needs.utf8 versus fixed-width string columns, when a dictionary column pays
off, and what the @jit decorator actually buys you (with a worked
Mandelbrot benchmark, including the compilation cost).asarray() corrupted arrays whose chunks overhang the shape. Above 16 MB
the fill goes chunk by chunk through SChunk.update_data(), and its guard
only rejected partitions smaller than their container — so a chunk sticking
out of the shape (e.g. shape (146, 23802) with chunks (147, 23802)) passed
and got a slice one row short, after which reading the array back failed with
"Error while getting the buffer". Thanks to @Zentrik.group_by returned the wrong min for a bool value column. The
per-group accumulator was seeded from the dtype's opposite identity, and bool
had none, so an all-True group reduced to False. Reachable with any plain
non-nullable bool column on the generic aggregation path.sort_by on a bool column raised, and on a signed-integer
column holding its dtype's minimum (-128 for int8) that row sorted as if it
were the largest. The descending key negated in the column's own dtype, where
bool has no unary minus and a narrow signed type wraps.add_column() after copy() backfilled one row short, and raised for a
variable-length column: the copy recorded its write watermark one below the
convention every other writer follows.t.where("a > 10") compared the stored sentinel, so any sentinel satisfying
the predicate (null_value=999 against > 10) came back as a match. The
operator form (t.where(t.a > 10)) was always correct. Fixed for both storages.uint8 ndarray column came back as bool. The bool → uint8
widening that sentinel storage needs was undone by dtype rather than by
whether it had been applied, so a column declared uint8 was truncated to
flags.~ on a nullable bool column selected its nulls. SQL WHERE semantics
say a null satisfies neither a predicate nor its negation; the mask path
inverted the stored False fill instead. (The sentinel path was already
correct, via its == 0 rewrite.)to_csv compared
against the sentinel to find nulls, so a mask column wrote its fill as if it
were data, and from_csv had nothing to put in an empty field and raised.
Both go through the sidecar now: an empty CSV field is a null in either
direction. Sentinel columns keep writing their sentinel, unchanged.sum() on a sorted view could return NaN from a column whose
nulls are not NaN, and unique() could report the fill as data while
dropping a real value. Sentinel columns were unaffected.convert_nulls() flattened a nested column, even when it had nothing to
convert: the schema copy it makes dropped both the table metadata and the
logical parent of a nested group, so a struct column came back as its leaves.
An in-place conversion on a persistent table wrote that flattened schema to
disk. Saving and reopening a nested table dropped the same parent, which is
fixed alongside it.sort_by mis-ordered the widest integers. The key was built
by negating, and negation has a fixed point: int64's minimum sorted as if
it were the largest, and a uint64 above 2**63 wrapped negative and sorted
below small values.UnicodeEncodeError on Windows. Both directions are UTF-8 now, and reading
absorbs a byte-order mark if one is present.col[key] = value. A
datetime was never encoded to the stored int64, so every key form failed;
extend() was unaffected. ISO strings and datetime64 are accepted too.col[0:2] = 7 — and the same write through a boolean mask or an index list —
failed with TypeError: iteration over a 0-d array, because the write path
looked for nulls inside a value that was a single cell rather than a batch.
Scalar broadcast works again, and col[0:2] = None now makes every selected
row null.extend() from another table lost nulls between storages. Copying rows
from a mask-backed column into a sentinel-backed one (or the reverse, or
between two sentinels reserving different values) wrote whatever stood in for
the null as real data. Nullity is translated now.None. t[i],
iteration and repr surfaced the placeholder that occupies a null slot, which
is not part of the format contract — and disagreed with a vlstring column in
the same row, which already read None. Sentinel columns still show their
sentinel, which is the value you chose.to_numpy(masked=True) and dropna() raised on a nullable dictionary
column, which reported its nulls per physical slot rather than per live row.nan+0j rather than as
missing.ndarray of bool forgot it had been widened when reopened, so
converting it to mask storage left it uint8, and the guard against a
dtype-changing in-place conversion on a persistent table stopped firing.convert_nulls(to="sentinel") refused over a value in a deleted row. The
collision check scanned physical slots, so a proposed sentinel present only in
a row already deleted — unreadable, and dropped by the next compact() —
blocked the conversion. Only live rows are consulted now, and the row named in
the refusal is the logical one the caller can index rather than a physical
slot.This release is the string-support milestone: string expressions and DSL kernels now run on miniexpr, utf8() and dictionary() columns gain full indexi
This release is the string-support milestone: string expressions and DSL
kernels now run on miniexpr, utf8() and dictionary() columns gain full
indexing, comparisons, and a documented conversion pair, and NumPy's
StringDType is understood by the array constructors. Alongside, slicing
with plain keys is up to 1.7x faster, a new blosc2.random module provides
chunk-parallel NumPy-quality random constructors, and the optimization-tips
guide gained two new tips and refreshed figures.
String-valued expressions and DSL kernels over fixed-width <Un
arrays now run on miniexpr instead of falling back to NumPy.
Concatenation (arr + "suffix", "prefix=" + arr) plus lower,
upper, strip/lstrip/rstrip, removeprefix, removesuffix,
replace, substr and split_part all produce string results, and
@blosc2.dsl_kernel accepts method syntax (name.lower()) and tuple
unpacking (before, after = desc.split(sep, 1)), which are rewritten to
the DSL grammar. The output width is inferred by miniexpr and the
container is allocated from it, so nothing truncates — .dtype may be
wider than NumPy's exact answer, never narrower.
Bytes (S) arrays go through the same engine, with NumPy's S
semantics rather than <U's: ASCII-only case mapping (so upper/lower
keep the width instead of growing) and ASCII-only stripping. S and <U
operands do not mix in one expression, which is what NumPy does too.
Variable-width utf8() columns still use the NumPy path.
String expressions now work on utf8() columns.
t.where("name == 'x'"), startswith/endswith/contains and mixed predicates such as
t.where("(name == 'b') | (x > 2)") used to raise NotImplementedError;
only the operator form t[t.name == "x"] was available. A variable-length
column cannot be an expression operand (its offsets and data have
independent chunk grids, so the prefilter contract does not apply), so
these are evaluated span by span, each span materialized to a fixed-width
array whose width is rounded up to a power of two and handed to miniexpr.
Nulls are materialized to "" before any kernel sees them and re-masked
afterwards, so a null never satisfies a predicate — the same answer the
operator form gives.
Scalar comparisons on utf8() columns are 5-6x faster in expression
form. t.where("name == 'x'") (and !=, <, <=, >, >=, either
operand order) is now answered by the same raw-byte scan the operator form
t[t.name == "x"] uses, instead of decoding the column to fixed-width
first: 156 -> 28 ms over 1M short values, 268 -> 56 ms over 1M ~31-byte
values. Mixed expressions get whatever they can -- in
startswith(name, 'x') | (name == 'zz') the comparison takes the fast
path and startswith still decodes.
New blosc2.utf8_array(seq, spec=None) builds a UTF8Array from an
iterable of strings; UTF8Array is exported too. Previously the only
construction path was UTF8Array(spec) + .extend() + .flush(), which
was not exported at all.
df.apply(f, axis=1, engine=blosc2.jit) now runs row["colname"]
kernels that contain an if. Neither dispatch route could before:
tracing evaluated the branch over a whole column (truth value ... is ambiguous)
and the DSL parser rejected the subscript. Such references are
now rewritten into named parameters, so the function is compiled and every
branch runs. This is not string-specific — numeric row kernels with a
branch were equally blocked. String columns reach this route too, which
makes the pandas-3 "format room info" kernel run unmodified. Nulls in a
string column are rejected rather than substituted, since a row-wise kernel
over a null raises in pandas as well.
New blosc2.random module: seedable, NumPy-quality random NDArray
constructors. Each chunk gets its own independent SeedSequence-spawned
stream and is generated concurrently in a thread pool, giving full PCG64
quality with genuinely parallel generation (measured ~3x faster than
asarray(np.random.default_rng(...).random(...)) on a 100M-element array).
Covers 42 of numpy.random.Generator's 43 public methods (full
compatibility table in doc/reference/random.rst):
random, integers, normal, uniform, choicereplace=True only).beta, binomial, chisquare,exponential, f, gamma, geometric, gumbel, hypergeometric,laplace, logistic, lognormal, logseries, negative_binomial,noncentral_chisquare, noncentral_f, pareto, poisson, power,rayleigh, standard_cauchy, standard_exponential,standard_gamma, standard_normal, standard_t, triangular,vonmises, wald, weibull, zipf.shape + (k,), one drawdirichlet, multinomial,multivariate_hypergeometric, multivariate_normal.permutation, permuted, shuffle: unlike the rest of the module,shuffle additionally requires its argument toNDArray, since it mutates in place and returns None,bytes (returns raw bytes, not an NDArray).create_index() now works on utf8() columns, the last string flavour
without one. Both utf8 and dictionary are indexed by the alphabetical
rank of each value: sorting by rank is sorting by the decoded string, so an
int32 rank column drives the same machinery a numeric column uses. At 1M
rows / cardinality 20k: sort_by 424 ms -> 7.2 ms, sorted_slice 458 ms ->
43 ms, and the index is the cheapest of the three flavours to build (277 ms
against 867 ms for <U). Scalar comparisons are served from it too — utf8
== 29.0 ms -> 5.5 ms, < 34.6 ms -> 5.5 ms, and the dictionary operator
form t[t.c == v] 329.6 ms -> 8.4 ms. startswith/substring searches are
not accelerated (no index covers them), and ranks are frozen at build time,
so a value inserted ahead of existing ones sends the index stale until it is
rebuilt.
CTable.add_column() accepts values=, a sequence with one entry per
live row, as an alternative to backfilling from a declared default. This is
the supported way to land a result computed outside the table back into it,
which matters most for utf8() columns: string-returning expressions are
evaluated on fixed-width arrays, and the result previously had to be written
through the private t._cols[name].set_all(...). A declared default is still
honoured for rows appended later, so the two can be combined. values= is
checked against the constraints declared on the spec, like the constructor
and extend() are: without that, coercion to a fixed-width dtype would
truncate an over-long string to max_length instead of complaining.
blosc2.from_utf8() / blosc2.to_utf8() and UTF8Array.astype() make
the conversion between variable-length and fixed-width text an explicit,
documented pair. utf8 columns store and filter text compactly, but
string-returning expressions need miniexpr's compile-time output width, so
they run on fixed-width arrays; the rule is now written down (see "Computing
strings on a utf8 column" in the CTable reference) rather than left for
callers to discover. from_utf8() sizes the result to the longest value in
codepoints, counted from the raw bytes without decoding a row, so nothing
truncates and non-ASCII text does not over-allocate the 3-4x a byte-length
bound would.
The array constructors dispatch on NumPy's StringDType.
blosc2.asarray(np.array([...], dtype=StringDType())) used to raise
TypeError: data type 'StringDType()' not understood, and
blosc2.zeros(n, dtype=StringDType()) a malformed node ValueError; both
now return a UTF8Array, as do empty, ones and full, with the same
fill values NumPy uses ('', '', '1', str(fill_value)). The dispatch
is on the target dtype, so asarray(utf8_source, dtype="<U8") still gives
a fixed-width NDArray. StringDType still cannot back an NDArray — it keeps
each row's payload outside the array buffer and offers no buffer protocol,
so compressing that buffer would persist pointers — which is why the
variable-length container is what comes back.
UTF8Array gained .shape, .ndim, .size and __array__, so it now
satisfies the blosc2.Array protocol (.shape was the only member it
lacked) and np.asarray(arr) returns StringDType instead of silently
widening to a fixed-width <Un — for 200-character values that was 1600
bytes where the payload is 203, and a different dtype than arr[:] reported
for the same object.
Column.assign() works on utf8, vlstring, vlbytes, struct and object
columns. It previously raised TypeError: UTF8Array assignment index must be int, leaving no public way to overwrite a variable-length column's
values. These are now rewritten whole (one write per backing batch) rather
than row by row, which for the batched varlen columns would have rewritten a
whole batch per row.
@blosc2.jit dispatches control flow to the DSL, and the DSL engine
widened its operand and function coverage. miniexpr's prefilter now
gathers blocks directly from raw NumPy buffers instead of converting
operands with asarray(), and Series are accepted as DSL operands.
np.foo(...) calls inside a jitted function are rewritten to the bare
names miniexpr recognizes, np.sign is supported, and np.square,
np.negative, np.positive and np.reciprocal are dispatched. A
configured blosc2.jit(...) is accepted as a pandas engine.
C-Blosc2 bumped to 3.3.1 (bundled; min version 3.3.0).
dict_store[code] decompresses a whole msgpack batch, so reads andsort_by cost O(N) decompressions. At 1M rows an unindexedsort_by drops from 236 s to 713 ms, and a full column read from 44 s (atsort_by on a dictionary column sorts int32 ranks, not decodedsort_by(view=True) from 247 ms tokind=BUCKET indexes no longer cost more than the scan they replace.float64 6.3 ms -> 77.9 ms before, 6.6 ms after).add_computed_column,add_generated_column, assign, apply, lazyudf, with a stringNotImplementedError naming the columnDTypePromotionError and a ValueError: malformed node or string ... StringDType(), neither of which named the column or the fix.process_key()arr[2:7, :50]-style tuple: in theslice(k, k+1) with negative-index and@blosc2.jit raised when a storage kwarg and an execution-tuning kwarg@blosc2.jit(jit=False, cparams=...) ended inblosc2.asarray(retval, jit=False, ...), which rejects the tuning kwargs.asarray() now; the function has already run, soadd_computed_column(name, kernel, inputs=["utf8_col"])str(table)ValueError: malformed node or string. The kernel is now refused atmin()/max() read from a column index returned the wrong value. Twomin() reported it — wrong ondelete()create_index on utf8() and dictionary() columns accepted any indexIndexKind.FULL reaches a rank index. kind now defaults toFULL for these two column kinds (BUCKET elsewhere, unchanged) and raisesValueError when another kind is requested explicitly. Previouslycreate_index("category") on a dictionary column built an unused BUCKETUTF8Array, dictionary and varlen scalar columnsFalse: none defined them, so column == "value" fellUTF8Array and DictionaryColumn answer a scalardictcol != value raised IndexError on any table with capacityTrue.UTF8Array (blosc2.lazyexpr("'x=' + a", {"a": arr})) produced correct values down the wrong path — widened to fixed-width<Un and evaluated by the NumPy fallback, never reaching miniexpr, ignoringUTF8Array.add_column() on a varlen column left it short on tables with deleteddelete() raised IndexError.add_column() on a dictionary() columnAttributeError from deep inside the fixed-width path; it now raisesTypeError naming the limitation.blosc2.utf8(null_value="\x00") is rejected. NumPy does not match a loneStringDType array ("\x00x" and "a\x00b" compare fine), so'__BLOSC2_NULL__' was never affected.utf8() leaves can be filtered. t.where("trip.name == 'x'") raised NotImplementedError on a utf8 leaf, while the same query on a<Un, bytes() or dictionary() leaf worked — utf8 was the only flavourstartswith/upper andsum(where=).SChunk slices were broken for typesizes above 255 bytes. c-blosc2'sblosc2_schunk_get_slice_buffer() derives the getitem for a partiallyschunk->typesize, which the<U64 NDArray is aarr.schunk[1:4] hit it. Partial reads now count inblosc2_getitem_bytes_ctx() (upstream c-blosc2 fix forcreate_index() on a string column made every query on it return zero(min, max, flags) record, so a <Un column makes it8n + 1 bytes: 257 for max_length=32, which is the default width forstr annotation. Past 255 bytes c-blosc2 records the chunk typesize8*max_length + 1 > 255 (31 works, 32 does not); summary, bucket,partial and full indexes were affected, opsi was not. A short spana[(True, :)] raisedValueError from the fancy-index path, which cannot handle the 0-d boolnp.newaxis-like dimension of length 1 (all True) or 0 (any False), placed__getitem__None and drops the rest, covering bool,np.bool_ and 0-d bool arrays. Bare a[True] was already correct.start > stop crashed with ValueError: negative dimensions are not allowed. The slicing fast path normalized slices withslice.indices(), which leaves a[100:50] as (100, 50); ndindex clamps(0, 0), and without that clamp theblosc2.pack_tensor() failed on 0-dim arrays (e.g. a scalarnp.array(17)); 0-dim inputs are now packed correctly.SChunk.meta.get(key, default) recursed forever when the key was<Un, instead of silently falling backupper/lower preserve the width, and a string resultblosc2.random constructors with NumPy integer scalar arguments (e.g.rng.integers(np.int64(3))) no longer produce wrong shapes.engine=blosc2.jit with Series operands no longer crashes onaxis=1, and an already-jitted function is not jitted a second time.doc/guides/optimization_tips.md:blosc2.argsort isA correctness release: lazy indexing and reductions now follow NumPy in a batch of cases where they quietly did not, the stores close several cross-process read races, and wheels finally ship usable C-Blosc2 development files. Bundled C-Blosc2 moves to 3.3.2.
expr[0] dropped every length-1 axis of the result, including ones the
index kept, so a (1, 4) expression indexed at [0] came back as (4,)
where NumPy gives (4,) only for the consumed axis and keeps the rest.
Only the dimensions the integer indices actually consumed are dropped now.
where() results, whose length is data-dependent, are left alone.
Closes #319.LazyUDF and broadcast operands mis-indexed on None. A None in the
key inserts an axis in the result but consumes none in the operand;
aligning it as if it did shifted every axis to its left by one, so
expr[None, 2] read the wrong operand region. Closes #403, #688.(a + b).sum() [key] evaluated the reduction over everything and then indexed; the
operands are sliced first now. Closes #457.t1 < t2 on datetime64/timedelta64 died with unknown type datetime64[s] and the error was re-raised rather than letting the NumPy
fallback try. They are compared as their underlying int64 counts, which is
exact, keeping the fast path. Mixed units and NaT decline that route and
fall back to NumPy, since raw counts would silently lie about both.
Closes #409.NDArray.nbytes reported the padded size. It now returns the logical
size * itemsize, matching NumPy, whenever the shape does not fill the
chunk grid exactly. cratio still measures the stored (padded) data, so
nbytes / cbytes need not equal cratio; use .schunk.nbytes for the
padded figure. Closes #544.CTable.where() applied a short boolean mask to the wrong rows. A mask
shorter than the live-row count was padded out to the physical length,
which aligned it with the underlying column and selected rows outside the
view. A mask no longer than the live-row count is now treated as logical —
entry i selects the i-th live row — with a short one simply leaving the
trailing rows unselected. Closes #607.EmbedStore and DictStore. Both resolved
a key under the store lock but read the data after releasing it, so a
concurrent writer could be caught mid-mutation: EmbedStore.__getitem__
returned bytes from a stale offset, and DictStore.__getitem__ opened an
external leaf that a concurrent overwrite had just removed or half-rewritten
(KeyError, or RuntimeError: Error while getting the buffer). The
resolve, the existence check and the open now share one lock; non-shared
stores skip it entirely. Fixes #691, #692.DictStore leaf is now atomic. __setitem__
removed the old leaf and rebuilt it at the same path, and the handle
__getitem__ returns holds no file descriptor — the C layer re-opens the
leaf by path for every chunk it decompresses — so a read already in flight
could open a truncated file. The new leaf is built beside its final name and
moved in with a single os.replace(), so every such re-open sees one
complete cframe or the other. A crash mid-write now leaves a stray .tmp
staging file rather than a partial leaf.TreeStore.close() swallowed inline handle failures. A CTable that
failed to flush left an archive whose row count disagreed with a varlen
column, reported only on read, long after close() said it succeeded. Every
handle still gets a chance to close and the store is still packed; the
failure is then re-raised.unpack_tensor() turned padding into a phantom column. np.dtype()
renames the empty-named padding fields of a structured descr ('' -> f2),
so a packed tensor with padding came back with an extra field. Closes #287.CMAKE_INSTALL_PREFIX; the absolute ones made C-Blosc2 generate
a blosc2.pc and exported targets pointing into the build tempdir, so
pkg-config and find_package(Blosc2) both failed against an installed
wheel. Verified by building and linking a C program against a wheel three
ways: pkg-config, Blosc2::blosc2_shared and Blosc2::blosc2_static.
miniexpr's license texts are mirrored into .dist-info/licenses, where PEP
639 tooling looks. Closes #627.libblosc2 instead of three, ~1.5 MB
smaller. C-Blosc2 set both VERSION and SOVERSION, and scikit-build-core
follows symlinks, so the fully versioned file — referenced by nothing but
the symlinks pointing at it — was shipped as a third full copy.-n auto --dist loadfile in
pytest.ini), 130s -> 35s locally, falling back to a serial run when
pytest-xdist is absent. The heavy tests, 58% of everything collected and
excluded from every push-time job, now run in a nightly workflow. The
network tests run once per push on a single Linux job instead of five times.select rather than extend-select,
so a ruff release widening its defaults no longer redefines what CI
enforces.A small hot-fix release for the Arrow interop work in 4.9.0: a real performance regression in dictionary-column export, and a clearer error message wh
A small hot-fix release for the Arrow interop work in 4.9.0: a real
performance regression in dictionary-column export, and a clearer error
message when opening a nonexistent CTable in append mode.
CTable.iter_arrow_batches() (and therefore to_arrow() and the Arrow__arrow_c_stream__) no longer recomputes theO(n_rows) scan that was repeatedO(n_rows / batch_size) times. The position array is now computed oncecompany) on a 1M-row table.CTable through the Arrow PyCapsuleCTable.select([...]) to project down to the columns you actuallyCTable with mode="a" at a path that doesn't exist yet nowFileNotFoundError ("mode='a' opens an existing table;This release is the string-support milestone: string expressions and DSL
kernels now run on miniexpr, utf8() and dictionary() columns gain full
indexing, comparisons, and a documented conversion pair, and NumPy's
StringDType is understood by the array constructors. Alongside, slicing
with plain keys is up to 1.7x faster, a new blosc2.random module provides
chunk-parallel NumPy-quality random constructors, and the optimization-tips
guide gained two new tips and refreshed figures.
String-valued expressions and DSL kernels over fixed-width <Un
arrays now run on miniexpr instead of falling back to NumPy.
Concatenation (arr + "suffix", "prefix=" + arr) plus lower,
upper, strip/lstrip/rstrip, removeprefix, removesuffix,
replace, substr and split_part all produce string results, and
@blosc2.dsl_kernel accepts method syntax (name.lower()) and tuple
unpacking (before, after = desc.split(sep, 1)), which are rewritten to
the DSL grammar. The output width is inferred by miniexpr and the
container is allocated from it, so nothing truncates — .dtype may be
wider than NumPy's exact answer, never narrower.
Bytes (S) arrays go through the same engine, with NumPy's S
semantics rather than <U's: ASCII-only case mapping (so upper/lower
keep the width instead of growing) and ASCII-only stripping. S and <U
operands do not mix in one expression, which is what NumPy does too.
Variable-width utf8() columns still use the NumPy path.
String expressions now work on utf8() columns. t.where("name == 'x'"), startswith/endswith/contains and mixed predicates such as
t.where("(name == 'b') | (x > 2)") used to raise NotImplementedError;
only the operator form t[t.name == "x"] was available. A variable-length
column cannot be an expression operand (its offsets and data have
independent chunk grids, so the prefilter contract does not apply), so
these are evaluated span by span, each span materialized to a fixed-width
array whose width is rounded up to a power of two and handed to miniexpr.
Nulls are materialized to "" before any kernel sees them and re-masked
afterwards, so a null never satisfies a predicate — the same answer the
operator form gives.
Scalar comparisons on utf8() columns are 5-6x faster in expression
form. t.where("name == 'x'") (and !=, <, <=, >, >=, either
operand order) is now answered by the same raw-byte scan the operator form
t[t.name == "x"] uses, instead of decoding the column to fixed-width
first: 156 -> 28 ms over 1M short values, 268 -> 56 ms over 1M ~31-byte
values. Mixed expressions get whatever they can -- in
startswith(name, 'x') | (name == 'zz') the comparison takes the fast
path and startswith still decodes.
New blosc2.utf8_array(seq, spec=None) builds a UTF8Array from an
iterable of strings; UTF8Array is exported too. Previously the only
construction path was UTF8Array(spec) + .extend() + .flush(), which
was not exported at all.
df.apply(f, axis=1, engine=blosc2.jit) now runs row["colname"]
kernels that contain an if. Neither dispatch route could before:
tracing evaluated the branch over a whole column (truth value ... is ambiguous) and the DSL parser rejected the subscript. Such references are
now rewritten into named parameters, so the function is compiled and every
branch runs. This is not string-specific — numeric row kernels with a
branch were equally blocked. String columns reach this route too, which
makes the pandas-3 "format room info" kernel run unmodified. Nulls in a
string column are rejected rather than substituted, since a row-wise kernel
over a null raises in pandas as well.
New blosc2.random module: seedable, NumPy-quality random NDArray
constructors. Each chunk gets its own independent SeedSequence-spawned
stream and is generated concurrently in a thread pool, giving full PCG64
quality with genuinely parallel generation (measured ~3x faster than
asarray(np.random.default_rng(...).random(...)) on a 100M-element array).
Covers 42 of numpy.random.Generator's 43 public methods (full
compatibility table in doc/reference/random.rst):
random, integers, normal, uniform, choice
(replace=True only).beta, binomial, chisquare,
exponential, f, gamma, geometric, gumbel, hypergeometric,
laplace, logistic, lognormal, logseries, negative_binomial,
noncentral_chisquare, noncentral_f, pareto, poisson, power,
rayleigh, standard_cauchy, standard_exponential,
standard_gamma, standard_normal, standard_t, triangular,
vonmises, wald, weibull, zipf.shape + (k,), one draw
per trailing vector): dirichlet, multinomial,
multivariate_hypergeometric, multivariate_normal.permutation, permuted, shuffle: unlike the rest of the module,
these are not chunk-parallel — whole-array shuffling is inherently
sequential, so they materialize the full array and shuffle it
single-threaded. shuffle additionally requires its argument to
already be an NDArray, since it mutates in place and returns None,
matching numpy.bytes (returns raw bytes, not an NDArray).create_index() now works on utf8() columns, the last string flavour
without one. Both utf8 and dictionary are indexed by the alphabetical
rank of each value: sorting by rank is sorting by the decoded string, so an
int32 rank column drives the same machinery a numeric column uses. At 1M
rows / cardinality 20k: sort_by 424 ms -> 7.2 ms, sorted_slice 458 ms ->
43 ms, and the index is the cheapest of the three flavours to build (277 ms
against 867 ms for <U). Scalar comparisons are served from it too — utf8
== 29.0 ms -> 5.5 ms, < 34.6 ms -> 5.5 ms, and the dictionary operator
form t[t.c == v] 329.6 ms -> 8.4 ms. startswith/substring searches are
not accelerated (no index covers them), and ranks are frozen at build time,
so a value inserted ahead of existing ones sends the index stale until it is
rebuilt.
CTable.add_column() accepts values=, a sequence with one entry per
live row, as an alternative to backfilling from a declared default. This is
the supported way to land a result computed outside the table back into it,
which matters most for utf8() columns: string-returning expressions are
evaluated on fixed-width arrays, and the result previously had to be written
through the private t._cols[name].set_all(...). A declared default is still
honoured for rows appended later, so the two can be combined. values= is
checked against the constraints declared on the spec, like the constructor
and extend() are: without that, coercion to a fixed-width dtype would
truncate an over-long string to max_length instead of complaining.
blosc2.from_utf8() / blosc2.to_utf8() and UTF8Array.astype() make
the conversion between variable-length and fixed-width text an explicit,
documented pair. utf8 columns store and filter text compactly, but
string-returning expressions need miniexpr's compile-time output width, so
they run on fixed-width arrays; the rule is now written down (see "Computing
strings on a utf8 column" in the CTable reference) rather than left for
callers to discover. from_utf8() sizes the result to the longest value in
codepoints, counted from the raw bytes without decoding a row, so nothing
truncates and non-ASCII text does not over-allocate the 3-4x a byte-length
bound would.
The array constructors dispatch on NumPy's StringDType.
blosc2.asarray(np.array([...], dtype=StringDType())) used to raise
TypeError: data type 'StringDType()' not understood, and
blosc2.zeros(n, dtype=StringDType()) a malformed node ValueError; both
now return a UTF8Array, as do empty, ones and full, with the same
fill values NumPy uses ('', '', '1', str(fill_value)). The dispatch
is on the target dtype, so asarray(utf8_source, dtype="<U8") still gives
a fixed-width NDArray. StringDType still cannot back an NDArray — it keeps
each row's payload outside the array buffer and offers no buffer protocol,
so compressing that buffer would persist pointers — which is why the
variable-length container is what comes back.
UTF8Array gained .shape, .ndim, .size and __array__, so it now
satisfies the blosc2.Array protocol (.shape was the only member it
lacked) and np.asarray(arr) returns StringDType instead of silently
widening to a fixed-width <Un — for 200-character values that was 1600
bytes where the payload is 203, and a different dtype than arr[:] reported
for the same object.
Column.assign() works on utf8, vlstring, vlbytes, struct and object
columns. It previously raised TypeError: UTF8Array assignment index must be int, leaving no public way to overwrite a variable-length column's
values. These are now rewritten whole (one write per backing batch) rather
than row by row, which for the batched varlen columns would have rewritten a
whole batch per row.
@blosc2.jit dispatches control flow to the DSL, and the DSL engine
widened its operand and function coverage. miniexpr's prefilter now
gathers blocks directly from raw NumPy buffers instead of converting
operands with asarray(), and Series are accepted as DSL operands.
np.foo(...) calls inside a jitted function are rewritten to the bare
names miniexpr recognizes, np.sign is supported, and np.square,
np.negative, np.positive and np.reciprocal are dispatched. A
configured blosc2.jit(...) is accepted as a pandas engine.
C-Blosc2 bumped to 3.3.1 (bundled; min version 3.3.0).
dict_store[code] decompresses a whole msgpack batch, so reads and
lexsort-based sort_by cost O(N) decompressions. At 1M rows an unindexed
sort_by drops from 236 s to 713 ms, and a full column read from 44 s (at
200k rows) to 193 ms.sort_by on a dictionary column sorts int32 ranks, not decoded
strings. A row's alphabetical rank orders exactly as its value does — the
trick the FULL index already used — so the sort key needs neither the decode
nor lexsort's string comparisons. Key construction drops from 106 ms to 21 ms
per 200k rows (cardinality 5000) and sort_by(view=True) from 247 ms to
157 ms. The filtered small-copy path, which had its own copy of the key
builder, now shares this one and picks up the same speedup.kind=BUCKET indexes no longer cost more than the scan they replace.
Scattered matches were read one bucket run at a time, re-decompressing the
same blocks many times, and the planner measured selectivity in buckets while
the cost is paid in blocks — a mask selecting 21% of buckets could touch 96%
of them. Affected every indexable dtype; the relative penalty was worst on
numerics (float64 6.3 ms -> 77.9 ms before, 6.6 ms after).add_computed_column,
add_generated_column, assign, apply, lazyudf, with a string
expression or a DSL kernel — raises NotImplementedError naming the column
and printing the three-line conversion, echoing the user's own expression
where there is one. Two of those paths previously failed with a raw NumPy
DTypePromotionError and a ValueError: malformed node or string ... StringDType(), neither of which named the column or the fix.process_key()
used to route every key through ndindex's general index machinery, ~50x
slower than needed for the common arr[2:7, :50]-style tuple: in the
scattered-read benchmark it accounted for 43% of the loop. Plain tuples (and
bare scalars) of slices and ints are now normalized directly — padding short
tuples, converting ints to slice(k, k+1) with negative-index and
out-of-bounds handling identical to ndindex's — and everything else
(strided or negative steps, ellipsis, newaxis, fancy arrays) still falls
through to ndindex. The optimization-tips guide's scattered-read tip went
from 0.627 s to 0.363 s on the plain-open variant.@blosc2.jit raised when a storage kwarg and an execution-tuning kwarg
were combined and the decorated function returned a NumPy array —
@blosc2.jit(jit=False, cparams=...) ended in
blosc2.asarray(retval, jit=False, ...), which rejects the tuning kwargs.
Only storage kwargs reach asarray() now; the function has already run, so
there is nothing left to tune.add_computed_column(name, kernel, inputs=["utf8_col"])
was accepted, after which every read of that column and str(table)
raised ValueError: malformed node or string. The kernel is now refused at
registration, where the table is still untouched.min()/max() read from a column index returned the wrong value. Two
independent causes, both affecting every indexable dtype. The block summaries
cover the column's physical extent, so the capacity padding (zeros, empty
strings) was reduced along with the data and min() reported it — wrong on
any table whose row count is not exactly its slot capacity. And delete()
bumps a visibility epoch that nothing recorded, so deleted rows kept
contributing their values to the block they sat in. Whole blocks below the
live row count are still read from the sidecar; the block straddling the
boundary is now rescanned, and a deletion since the index was built makes the
shortcut stand down.create_index on utf8() and dictionary() columns accepted any index
kind and built one over the alphabetical ranks that no query would ever
consult — only IndexKind.FULL reaches a rank index. kind now defaults to
FULL for these two column kinds (BUCKET elsewhere, unchanged) and raises
ValueError when another kind is requested explicitly. Previously
create_index("category") on a dictionary column built an unused BUCKET
index by default.UTF8Array, dictionary and varlen scalar columns
returned a plain False: none defined them, so column == "value" fell
through to object identity. Silently wrong rather than an error. All now
return boolean masks; UTF8Array and DictionaryColumn answer a scalar
without decoding any row.dictcol != value raised IndexError on any table with capacity
padding: the negation was applied after the live-row intersection, turning
every dead slot True.UTF8Array (blosc2.lazyexpr("'x=' + a", {"a": arr})) produced correct values down the wrong path — widened to fixed-width
<Un and evaluated by the NumPy fallback, never reaching miniexpr, ignoring
the span budget and losing the utf8 container. They now run through the span
driver and return a UTF8Array.add_column() on a varlen column left it short on tables with deleted
rows. vlstring/vlbytes/utf8/struct/object columns are indexed by physical
position but the new column was filled with only as many entries as there
were live rows, so the first read after a delete() raised IndexError.
The dead slots are now filled too. add_column() on a dictionary() column
raised AttributeError from deep inside the fixed-width path; it now raises
TypeError naming the limitation.blosc2.utf8(null_value="\x00") is rejected. NumPy does not match a lone
NUL against a StringDType array ("\x00x" and "a\x00b" compare fine), so
every null mask would silently stop marking nulls. The default sentinel
'__BLOSC2_NULL__' was never affected.utf8() leaves can be filtered. t.where("trip.name == 'x'") raised NotImplementedError on a utf8 leaf, while the same query on a
<Un, bytes() or dictionary() leaf worked — utf8 was the only flavour
where a dotted name could not be queried at all. Dotted names are aliased to
safe identifiers before evaluation, but utf8 columns are outside the operand
namespace, so they never reached that rewrite; they are now aliased by the
utf8 driver itself. Covers scalar comparisons, startswith/upper and
friends, mixed numeric predicates and sum(where=).SChunk slices were broken for typesizes above 255 bytes. c-blosc2's
blosc2_schunk_get_slice_buffer() derives the getitem for a partially
covered chunk by dividing byte offsets by schunk->typesize, which the
chunk header contradicts once the typesize is capped: the unit changes
silently with the data, so blocks past the first could come back as
uninitialised memory. Reachable from ordinary data — an <U64 NDArray is a
256-byte typesize, so arr.schunk[1:4] hit it. Partial reads now count in
bytes via blosc2_getitem_bytes_ctx() (upstream c-blosc2 fix for
Blosc/c-blosc2#796, included in the bundled 3.3.1), which is unambiguous at
any typesize.create_index() on a string column made every query on it return zero
rows. Silently — adding an index, an optimization, changed the answer. A
segment summary is a (min, max, flags) record, so a <Un column makes it
8n + 1 bytes: 257 for max_length=32, which is the default width for
a plain str annotation. Past 255 bytes c-blosc2 records the chunk typesize
as 1, and the sidecar reader asked for spans in element units, so summaries
decoded to garbage and pruned every candidate away. The boundary is exactly
8*max_length + 1 > 255 (31 works, 32 does not); summary, bucket,
partial and full indexes were affected, opsi was not. A short span
read now raises instead of leaving the destination partly uninitialised.a[(True, :)] raised
ValueError from the fancy-index path, which cannot handle the 0-d bool
array ndindex expands tuple bools to. NumPy treats them as a single
np.newaxis-like dimension of length 1 (all True) or 0 (any False), placed
at the first bool's position with multiple bools collapsing; __getitem__
now rewrites the first bool to None and drops the rest, covering bool,
np.bool_ and 0-d bool arrays. Bare a[True] was already correct.start > stop crashed with ValueError: negative dimensions are not allowed. The slicing fast path normalized slices with
slice.indices(), which leaves a[100:50] as (100, 50); ndindex clamps
any empty positive-step slice to (0, 0), and without that clamp the
result shape went negative. The fast path now clamps empty slices exactly
like ndindex.blosc2.pack_tensor() failed on 0-dim arrays (e.g. a scalar
np.array(17)); 0-dim inputs are now packed correctly.SChunk.meta.get(key, default) recursed forever when the key was
absent; it now returns the default.<Un, instead of silently falling back
to byte shuffle), upper/lower preserve the width, and a string result
containing the utf8 null sentinel is refused rather than corrupting null
masks.blosc2.random constructors with NumPy integer scalar arguments (e.g.
rng.integers(np.int64(3))) no longer produce wrong shapes.engine=blosc2.jit with Series operands no longer crashes on
axis=1, and an already-jitted function is not jitted a second time.doc/guides/optimization_tips.md:
generating arrays with DSL kernels, and broadcasting small operands into
large on-disk arrays; the guide's figures were regenerated against the
reference machine and the benchmark harness now checks c-blosc2's
pread/handle-cache work.blosc2.argsort is
now documented.This release is about cooperation: CTable now speaks the tabular ecosystem's own protocols instead of asking it to speak blosc2's. Arrow tools (pyarro
This release is about cooperation: CTable now speaks the tabular
ecosystem's own protocols instead of asking it to speak blosc2's. Arrow
tools (pyarrow, DuckDB, Polars, and pandas >= 3.0 via DataFrame.from_arrow())
can consume or produce a CTable directly through the Arrow PyCapsule
interface; a new utf8() string column stores text in Arrow's own
offsets+bytes layout and reads back as NumPy StringDType; and
engine=blosc2.jit now runs correctly inside pandas 3 itself. Alongside
that, CTable gained a proper missing-data story (fillna/dropna,
null-safe arithmetic and comparisons) and a pandas-3-style chaining API
(assign()/col(), UDF aggregations, CTable.apply()).
CTable.__arrow_c_stream__ lets
pyarrow, DuckDB, and Polars consume a CTable directly as a stream of
record batches, with bounded memory — no to_arrow()/copy step
required. pandas >= 3.0 can do the same via the new
pandas.DataFrame.from_arrow() classmethod (the plain pd.DataFrame(t)
constructor does not use this protocol). CTable.from_arrow() now
accepts any object implementing the same protocol on ingest
(single-argument form), in addition to the existing (schema, batches)
form — including Arrow's string_view layout (Polars' default string
export type), which raised TypeError before this release.blosc2.utf8(): a new column type for high-cardinality/free-text
strings, storing each column as two companion NDArrays — int64 row
offsets plus a UTF-8 byte blob — the same layout Arrow uses for
large_string. A row costs exactly its encoded byte length (7-13x
smaller uncompressed than fixed-width string() on high-cardinality
text), and reads materialize as NumPy StringDType arrays (NumPy >=
2.0 required; older NumPy falls back to vlstring on Arrow/Parquet
import with a clear message). Full query surface: comparisons,
where(), sort_by, group_by keys, fillna, and Arrow export
(large_string, sentinel-null mask) / import (Arrow/Parquet string
columns now default to utf8 instead of vlstring). Measured on the
1e7-row NYC-taxi company column: ingest 3622 ms -> 598.6 ms
(6.05x faster, only 1.22x slower than string() and 2.63x faster
than vlstring()), full-column read 2472.6 ms -> 165.3 ms (14.96x
faster), equality filter ~1900 ms -> 162 ms (~11.7x faster), and
groupby-key factorization 3304 ms -> 558 ms (2.83x, down from a
16.9x-slower initial fallback, now faster than fixed-width string()
keys). See the new "Choosing a string column type" guide in the
CTable reference and examples/ctable/utf8_strings.py. Known gaps:
string-expression filters (t.where("name == 'x'")) and
create_index on utf8 columns still raise a clear
NotImplementedError; use fixed-width string() if you need those.engine=blosc2.jit for
DataFrame.apply now returns a properly indexed DataFrame/Series
under pandas 3.0.3's default raw=False (previously a raw NumPy
array, so results only matched by value, never by type).
Series.map(func, engine=blosc2.jit) is now implemented (it
previously always raised NotImplementedError). Non-numeric columns
now raise a clear ValueError instead of a deep numexpr error. See
the guide "Using Blosc2 as a pandas engine" and
bench/bench_pandas_engine.py.CTable.assign(**named_exprs): return a view with additional computed
columns, without mutating the table or copying column data. Pairs with
the new blosc2.col(name) — an unbound column expression that defers
operator replay until it's bound to a table (assign(), t[...],
where()) — to write pandas-3-style chains:
t.assign(profit=col("revenue") - col("cost"))[col("profit") > 0].sort_by("profit", ascending=False).head(10).Column.fillna(value) replaces sentinel/None
values for scalar, dictionary, and varlen-scalar columns;
CTable.dropna(subset=None) returns a view excluding rows where any
nullable column (or a chosen subset) is null. Column arithmetic
(+ - * / // % **) and comparisons (< <= > >= == !=) on nullable
int/timestamp/bool columns now propagate nulls instead of operating on
the raw sentinel: arithmetic promotes to float64/NaN, comparisons
follow SQL WHERE semantics (a null operand never satisfies any
comparison) — fixing filters like t[t.x < 0] wrongly matching null
rows. Also fixes null detection for timestamp columns
(is_null()/null_count()/dropna() previously missed every
NaT).group_by().agg() accepts a custom callable as
the op via the named form (output_name=(column, callable[, dtype]));
it receives each group's live, non-null values as a 1-D NumPy array.
CTable.apply(func, columns=None, dtype=None, engine="auto") applies
a UDF across the table's live rows, sugar over blosc2.lazyudf().
group_by(engine=...) now accepts "auto"/"numpy" explicitly
alongside the existing default.CTable views are now read-only for value writes: Column.__setitem__
and Column.assign() raise ValueError on a view, pointing at
take()/copy() as the escape hatch (structural mutations already
raised; this closes the one unguarded cell-write path).NDArray.iter_sorted()/argsort() on a FULL-indexed array now reads
the sidecar range directly instead of building the full permutation —
~52x faster and ~193x less memory on a 20M-element array for
iter_sorted(start=-k)-style tail queries. See the new optimization
tip.group_by() (fixed-width string() keys) now factorizes
via an exact hash of each row's raw bytes instead of a NumPy
UTF-32 argsort, with a vectorized collision-checked verify pass
keeping the result bit-identical: 1157 ms -> 737 ms on a 1e7-row
benchmark. Every caller benefits automatically; no engine= switch
involved.TreeStore.@blosc2.dsl_kernel-decorated function crashing unconditionally
when passed as a groupby UDF aggregation (g.agg(name=(col, dsl_kernel_fn))): it now runs like the equivalent undecorated callable.CTable.head()/tail() silently discarding row order when called
on a lazily-sorted view (e.g. t.sort_by("col", ascending=False) on a
view, or any .sort_by() result chained off a prior filter): they
ignored _cached_live_positions and built a plain physical-order mask
instead, so t.where(...).sort_by("x", ascending=False).head(10) came
back in the wrong order.NameError when nan/inf scalars appeared in lazy
expressions; ShapeInferencer no longer ignores user-provided shapes
for nan/inf.CTable.from_arrow() raising TypeError: No blosc2 spec for Arrow type DataType(string_view) on any Arrow string_view/binary_view
column — the layout Polars exports by default through the PyCapsule
protocol. string_view/binary_view now import exactly like
string/large_string/binary/large_binary everywhere a column's
Arrow type is inspected (schema inference, null-sentinel selection,
list/struct/dictionary value types).This release is about cooperation: CTable now speaks the tabular
ecosystem's own protocols instead of asking it to speak blosc2's. Arrow
tools (pyarrow, DuckDB, Polars, and pandas >= 3.0 via DataFrame.from_arrow())
can consume or produce a CTable directly through the Arrow PyCapsule
interface; a new utf8() string column stores text in Arrow's own
offsets+bytes layout and reads back as NumPy StringDType; and
engine=blosc2.jit now runs correctly inside pandas 3 itself. Alongside
that, CTable gained a proper missing-data story (fillna/dropna,
null-safe arithmetic and comparisons) and a pandas-3-style chaining API
(assign()/col(), UDF aggregations, CTable.apply()).
CTable.__arrow_c_stream__ letsCTable directly as a stream ofto_arrow()/copy steppandas.DataFrame.from_arrow() classmethod (the plain pd.DataFrame(t)CTable.from_arrow() now(schema, batches)string_view layout (Polars' default stringTypeError before this release.blosc2.utf8(): a new column type for high-cardinality/free-textlarge_string. A row costs exactly its encoded byte length (7-13xstring() on high-cardinalityStringDType arrays (NumPy >=vlstring on Arrow/Parquetwhere(), sort_by, group_by keys, fillna, and Arrow exportlarge_string, sentinel-null mask) / import (Arrow/Parquet stringutf8 instead of vlstring). Measured on thecompany column: ingest 3622 ms -> 598.6 msstring() and 2.63x fastervlstring()), full-column read 2472.6 ms -> 165.3 ms (14.96xstring()CTable reference and examples/ctable/utf8_strings.py. Known gaps:t.where("name == 'x'")) andcreate_index on utf8 columns still raise a clearNotImplementedError; use fixed-width string() if you need those.engine=blosc2.jit forDataFrame.apply now returns a properly indexed DataFrame/Seriesraw=False (previously a raw NumPySeries.map(func, engine=blosc2.jit) is now implemented (itNotImplementedError). Non-numeric columnsValueError instead of a deep numexpr error. Seebench/bench_pandas_engine.py.CTable.assign(**named_exprs): return a view with additional computedblosc2.col(name) — an unbound column expression that defersassign(), t[...],where()) — to write pandas-3-style chains:t.assign(profit=col("revenue") - col("cost"))[col("profit") > 0].sort_by("profit", ascending=False).head(10).Column.fillna(value) replaces sentinel/NoneCTable.dropna(subset=None) returns a view excluding rows where any+ - * / // % **) and comparisons (< <= > >= == !=) on nullablefloat64/NaN, comparisonsWHERE semantics (a null operand never satisfies anyt[t.x < 0] wrongly matching nullis_null()/null_count()/dropna() previously missed everyNaT).group_by().agg() accepts a custom callable asoutput_name=(column, callable[, dtype]));CTable.apply(func, columns=None, dtype=None, engine="auto") appliesblosc2.lazyudf().group_by(engine=...) now accepts "auto"/"numpy" explicitlyCTable views are now read-only for value writes: Column.__setitem__Column.assign() raise ValueError on a view, pointing attake()/copy() as the escape hatch (structural mutations alreadyNDArray.iter_sorted()/argsort() on a FULL-indexed array now readsiter_sorted(start=-k)-style tail queries. See the new optimizationgroup_by() (fixed-width string() keys) now factorizesengine= switchTreeStore.@blosc2.dsl_kernel-decorated function crashing unconditionallyg.agg(name=(col, dsl_kernel_fn))): it now runs like the equivalent undecorated callable.CTable.head()/tail() silently discarding row order when calledt.sort_by("col", ascending=False) on a.sort_by() result chained off a prior filter): they_cached_live_positions and built a plain physical-order maskt.where(...).sort_by("x", ascending=False).head(10) cameNameError when nan/inf scalars appeared in lazyShapeInferencer no longer ignores user-provided shapesnan/inf.CTable.from_arrow() raising TypeError: No blosc2 spec for Arrow type DataType(string_view) on any Arrow string_view/binary_viewstring_view/binary_view now import exactly likestring/large_string/binary/large_binary everywhere a column'sA small hot-fix release for the Arrow interop work in 4.9.0: a real
performance regression in dictionary-column export, and a clearer error
message when opening a nonexistent CTable in append mode.
CTable.iter_arrow_batches() (and therefore to_arrow() and the Arrow
PyCapsule interchange, __arrow_c_stream__) no longer recomputes the
full live-row-position array from scratch on every batch, for every
dictionary column — an O(n_rows) scan that was repeated
O(n_rows / batch_size) times. The position array is now computed once
per export call instead. Measured 6-14x faster export for
dictionary-encoded string columns (e.g. company) on a 1M-row table.CTable through the Arrow PyCapsule
protocol (DuckDB, pyarrow, Polars, pandas): the raw Arrow C Stream
interface has no column-projection pushdown, so a consumer that only
needs a few columns still triggers export of every column in the table.
Use CTable.select([...]) to project down to the columns you actually
need before handing the table to the consumer, particularly if any
column is an expensive nested/list type.SChunk slices were broken for typesizes above 255 bytes. c-blosc2's
blosc2_schunk_get_slice_buffer() derives the getitem for a partially
covered chunk by dividing byte offsets by schunk->typesize, which the
chunk header contradicts once the typesize is capped. Across 153 slice
shapes at typesize 256, 150 raised "Error while getting the slice" and
the 3 single-element ones returned the wrong bytes with no error at all.
Reachable from ordinary data -- an <U64 NDArray is a 256-byte typesize,
so arr.schunk[1:4] hit it. Fixed upstream in Blosc/c-blosc2#796, so this
release requires a c-blosc2 that carries that fix.create_index() on a string column made every query on it return zero
rows. Silently -- adding an index, an optimization, changed the answer.
A segment summary is a (min, max, flags) record, so a <Un column makes
it 8n + 1 bytes: 257 for max_length=32, which is the default width
for a plain str annotation. Past 255 bytes c-blosc2 records the chunk
typesize as 1, and the sidecar reader asked for spans in element units, so
summaries decoded to garbage and pruned every candidate away. The boundary
is exactly 8*max_length + 1 > 255 (31 works, 32 does not); summary,
bucket, partial and full indexes were affected, opsi was not.
A short span read now raises instead of leaving the destination partly
uninitialised.blosc2_getitem_ctx() for operand blocks in element units, which the chunk
then read as a byte range: every block past the first was uninitialised
memory. arr == "hello" over 1200 rows of <U64 (256 bytes) matched 1 row
instead of 400, non-deterministically and with no error raised. Affects any
dtype whose itemsize exceeds 255 bytes; <U64 is the first fixed-width
string that hits it.CTable with mode="a" at a path that doesn't exist yet now
raises a clear FileNotFoundError ("mode='a' opens an existing table;
use mode='w' to create a new one") instead of silently falling through
and creating a new, empty table.Read-only memory mapping for CTable stores: CTable.open() (and FileTableStorage) gain an mmap_mode="r" parameter, mirroring blosc2.open(). All members
CTable stores: CTable.open() (and
FileTableStorage) gain an mmap_mode="r" parameter, mirroring
blosc2.open(). All members of a read-only store — scalar, list, varlen
and dictionary columns alike — are then read from mapped pages; for .b2z
archives, in place at their offsets inside the single mapped container
file. With several concurrent readers on one file this pays off quickly:
2.5x/4.4x/4.5x faster wall time for 1/4/8 readers in our benchmark
(bench/optim_tips/tip_10_mmap_many_readers.py).CTable.extend() when passed an NDArray.vlmeta: schunk_from_cframe()/ndarray_from_cframe() with copy=False
(the default) returned objects pointing into the caller's bytes buffer
without keeping it alive, so a temporary cframe (e.g.
ndarray_from_cframe(response.content)) could be reclaimed under the live
object, corrupting reads. The buffer is now pinned on the returned object.
Also, vlmeta read paths (__getitem__/__len__/__iter__) now raise
ReferenceError on an orphaned owner instead of segfaulting, matching the
write paths.BatchArray.delete() / ObjectArray.delete(): negative-step slices
(e.g. del arr[3:0:-1]) deleted chunks in ascending order, shifting the
indices of chunks still to be deleted and removing the wrong ones (or
raising RuntimeError).ListArray.extend_arrow(): Arrow chunks were appended to the backend
without flushing pending cells first, reordering unflushed rows after the
new ones.stack() with a negative axis inserted the new dimension
one position too early, so a lazyexpr's reported .shape disagreed with
its computed result; vecdot() also normalized positive axes as if they
were negative.matmul(): broadcast (size-1) operand batch dims were sliced with
the result-chunk coordinates, producing empty slices when the broadcast
dim spans several result chunks.DictStore.__setitem__(): overwrite semantics depended on value size —
embedded keys refused overwrite ("already exists"), while an
embedded-to-external overwrite double-stored the key and resurrected the
stale embedded value after a delete. Assignments now behave uniformly
dict-like, dropping any previous value.cparams=None or dparams=None to NDArray
constructors crashed; both now mean "defaults".blosc2.open() recurse ~250 times (silently swallowed) on every
.b2z/.b2d open; opening a .b2z is now ~10x faster under allocation
tracing..b2z file and on using mmap_mode="r" with many
concurrent readers.This release is about cooperation: CTable now speaks the tabular
ecosystem's own protocols instead of asking it to speak blosc2's. Arrow
tools (pyarrow, DuckDB, Polars, and pandas >= 3.0 via DataFrame.from_arrow())
can consume or produce a CTable directly through the Arrow PyCapsule
interface; a new utf8() string column stores text in Arrow's own
offsets+bytes layout and reads back as NumPy StringDType; and
engine=blosc2.jit now runs correctly inside pandas 3 itself. Alongside
that, CTable gained a proper missing-data story (fillna/dropna,
null-safe arithmetic and comparisons) and a pandas-3-style chaining API
(assign()/col(), UDF aggregations, CTable.apply()).
CTable.__arrow_c_stream__ lets
pyarrow, DuckDB, and Polars consume a CTable directly as a stream of
record batches, with bounded memory — no to_arrow()/copy step
required. pandas >= 3.0 can do the same via the new
pandas.DataFrame.from_arrow() classmethod (the plain pd.DataFrame(t)
constructor does not use this protocol). CTable.from_arrow() now
accepts any object implementing the same protocol on ingest
(single-argument form), in addition to the existing (schema, batches)
form — including Arrow's string_view layout (Polars' default string
export type), which raised TypeError before this release.blosc2.utf8(): a new column type for high-cardinality/free-text
strings, storing each column as two companion NDArrays — int64 row
offsets plus a UTF-8 byte blob — the same layout Arrow uses for
large_string. A row costs exactly its encoded byte length (7-13x
smaller uncompressed than fixed-width string() on high-cardinality
text), and reads materialize as NumPy StringDType arrays (NumPy >=
2.0 required; older NumPy falls back to vlstring on Arrow/Parquet
import with a clear message). Full query surface: comparisons,
where(), sort_by, group_by keys, fillna, and Arrow export
(large_string, sentinel-null mask) / import (Arrow/Parquet string
columns now default to utf8 instead of vlstring). Measured on the
1e7-row NYC-taxi company column: ingest 3622 ms -> 598.6 ms
(6.05x faster, only 1.22x slower than string() and 2.63x faster
than vlstring()), full-column read 2472.6 ms -> 165.3 ms (14.96x
faster), equality filter ~1900 ms -> 162 ms (~11.7x faster), and
groupby-key factorization 3304 ms -> 558 ms (2.83x, down from a
16.9x-slower initial fallback, now faster than fixed-width string()
keys). See the new "Choosing a string column type" guide in the
CTable reference and examples/ctable/utf8_strings.py. Known gaps:
string-expression filters (t.where("name == 'x'")) and
create_index on utf8 columns still raise a clear
NotImplementedError; use fixed-width string() if you need those.engine=blosc2.jit for
DataFrame.apply now returns a properly indexed DataFrame/Series
under pandas 3.0.3's default raw=False (previously a raw NumPy
array, so results only matched by value, never by type).
Series.map(func, engine=blosc2.jit) is now implemented (it
previously always raised NotImplementedError). Non-numeric columns
now raise a clear ValueError instead of a deep numexpr error. See
the guide "Using Blosc2 as a pandas engine" and
bench/bench_pandas_engine.py.CTable.assign(**named_exprs): return a view with additional computed
columns, without mutating the table or copying column data. Pairs with
the new blosc2.col(name) — an unbound column expression that defers
operator replay until it's bound to a table (assign(), t[...],
where()) — to write pandas-3-style chains:
t.assign(profit=col("revenue") - col("cost"))[col("profit") > 0].sort_by("profit", ascending=False).head(10).Column.fillna(value) replaces sentinel/None
values for scalar, dictionary, and varlen-scalar columns;
CTable.dropna(subset=None) returns a view excluding rows where any
nullable column (or a chosen subset) is null. Column arithmetic
(+ - * / // % **) and comparisons (< <= > >= == !=) on nullable
int/timestamp/bool columns now propagate nulls instead of operating on
the raw sentinel: arithmetic promotes to float64/NaN, comparisons
follow SQL WHERE semantics (a null operand never satisfies any
comparison) — fixing filters like t[t.x < 0] wrongly matching null
rows. Also fixes null detection for timestamp columns
(is_null()/null_count()/dropna() previously missed every
NaT).group_by().agg() accepts a custom callable as
the op via the named form (output_name=(column, callable[, dtype]));
it receives each group's live, non-null values as a 1-D NumPy array.
CTable.apply(func, columns=None, dtype=None, engine="auto") applies
a UDF across the table's live rows, sugar over blosc2.lazyudf().
group_by(engine=...) now accepts "auto"/"numpy" explicitly
alongside the existing default.CTable views are now read-only for value writes: Column.__setitem__
and Column.assign() raise ValueError on a view, pointing at
take()/copy() as the escape hatch (structural mutations already
raised; this closes the one unguarded cell-write path).NDArray.iter_sorted()/argsort() on a FULL-indexed array now reads
the sidecar range directly instead of building the full permutation —
~52x faster and ~193x less memory on a 20M-element array for
iter_sorted(start=-k)-style tail queries. See the new optimization
tip.group_by() (fixed-width string() keys) now factorizes
via an exact hash of each row's raw bytes instead of a NumPy
UTF-32 argsort, with a vectorized collision-checked verify pass
keeping the result bit-identical: 1157 ms -> 737 ms on a 1e7-row
benchmark. Every caller benefits automatically; no engine= switch
involved.TreeStore.@blosc2.dsl_kernel-decorated function crashing unconditionally
when passed as a groupby UDF aggregation (g.agg(name=(col, dsl_kernel_fn))): it now runs like the equivalent undecorated callable.CTable.head()/tail() silently discarding row order when called
on a lazily-sorted view (e.g. t.sort_by("col", ascending=False) on a
view, or any .sort_by() result chained off a prior filter): they
ignored _cached_live_positions and built a plain physical-order mask
instead, so t.where(...).sort_by("x", ascending=False).head(10) came
back in the wrong order.NameError when nan/inf scalars appeared in lazy
expressions; ShapeInferencer no longer ignores user-provided shapes
for nan/inf.CTable.from_arrow() raising TypeError: No blosc2 spec for Arrow type DataType(string_view) on any Arrow string_view/binary_view
column — the layout Polars exports by default through the PyCapsule
protocol. string_view/binary_view now import exactly like
string/large_string/binary/large_binary everywhere a column's
Arrow type is inspected (schema inference, null-sentinel selection,
list/struct/dictionary value types).New locking storage parameter (and the BLOSC_LOCKING environment variable to enable it fleet-wide) serializes accesses to an on-disk SChunk/NDArray/Em
locking storage parameter (and the BLOSC_LOCKING environment
variable to enable it fleet-wide) serializes accesses to an on-disk
SChunk/NDArray/EmbedStore/DictStore against other handles and other
processes, via a small sidecar lock file (.b2lock). Advisory: every
handle touching the container must opt in.SChunk.holding_lock() / NDArray.holding_lock(): a context manager to
hold the exclusive lock across several operations, making a multi-step
mutation atomic to other locked handles.SChunk.refresh(), mirroring the existing NDArray.refresh().NDArray.append(): it read the cached,
unrefreshed shape before computing the resize target, so under
concurrent growth/shrink — even inside holding_lock() — another writer's
just-appended data could be silently deleted.EmbedStore and DictStore (.b2d) now support cross-process writers
under locking: transactional writes plus key-map re-sync, so readers
follow keys added or removed by another process.DictStore.to_b2z() (and TreeStore, which inherits from it) now replaces
the target file atomically, so concurrent readers always see either the old
or the new archive, never a torn one.NDArray handle
opened before a resize() made through another handle follows the new
shape on its next data access, or via the new explicit NDArray.refresh().mmap_mode, Windows
in-use-file rename).detect_aligned_chunks() (used internally to fast-path aligned
slice reads/writes): a floor-division undercounted the chunk grid for
arrays whose shape isn't a multiple of the chunk shape, which could
silently return the wrong chunk's data for an otherwise-aligned slice
with a nonzero start in an earlier dimension.manylinux2014 (CentOS 7, glibc
2.17, GCC 10.2) to manylinux_2_28 (AlmaLinux 8, glibc 2.28, GCC 12),
fixing a build failure with NumPy >=2.5 which requires GCC >=10.3.CTable stores: CTable.open() (and
FileTableStorage) gain an mmap_mode="r" parameter, mirroring
blosc2.open(). All members of a read-only store — scalar, list, varlen
and dictionary columns alike — are then read from mapped pages; for .b2z
archives, in place at their offsets inside the single mapped container
file. With several concurrent readers on one file this pays off quickly:
2.5x/4.4x/4.5x faster wall time for 1/4/8 readers in our benchmark
(bench/optim_tips/tip_10_mmap_many_readers.py).CTable.extend() when passed an NDArray.vlmeta: schunk_from_cframe()/ndarray_from_cframe() with copy=False
(the default) returned objects pointing into the caller's bytes buffer
without keeping it alive, so a temporary cframe (e.g.
ndarray_from_cframe(response.content)) could be reclaimed under the live
object, corrupting reads. The buffer is now pinned on the returned object.
Also, vlmeta read paths (__getitem__/__len__/__iter__) now raise
ReferenceError on an orphaned owner instead of segfaulting, matching the
write paths.BatchArray.delete() / ObjectArray.delete(): negative-step slices
(e.g. del arr[3:0:-1]) deleted chunks in ascending order, shifting the
indices of chunks still to be deleted and removing the wrong ones (or
raising RuntimeError).ListArray.extend_arrow(): Arrow chunks were appended to the backend
without flushing pending cells first, reordering unflushed rows after the
new ones.stack() with a negative axis inserted the new dimension
one position too early, so a lazyexpr's reported .shape disagreed with
its computed result; vecdot() also normalized positive axes as if they
were negative.matmul(): broadcast (size-1) operand batch dims were sliced with
the result-chunk coordinates, producing empty slices when the broadcast
dim spans several result chunks.DictStore.__setitem__(): overwrite semantics depended on value size —
embedded keys refused overwrite ("already exists"), while an
embedded-to-external overwrite double-stored the key and resurrected the
stale embedded value after a delete. Assignments now behave uniformly
dict-like, dropping any previous value.cparams=None or dparams=None to NDArray
constructors crashed; both now mean "defaults".blosc2.open() recurse ~250 times (silently swallowed) on every
.b2z/.b2d open; opening a .b2z is now ~10x faster under allocation
tracing..b2z file and on using mmap_mode="r" with many
concurrent readers.Tagging python-blosc2 version 4.7.0
Tagging python-blosc2 version 4.7.0
locking storage parameter (and the BLOSC_LOCKING environment
variable to enable it fleet-wide) serializes accesses to an on-disk
SChunk/NDArray/EmbedStore/DictStore against other handles and other
processes, via a small sidecar lock file (.b2lock). Advisory: every
handle touching the container must opt in.SChunk.holding_lock() / NDArray.holding_lock(): a context manager to
hold the exclusive lock across several operations, making a multi-step
mutation atomic to other locked handles.SChunk.refresh(), mirroring the existing NDArray.refresh().NDArray.append(): it read the cached,
unrefreshed shape before computing the resize target, so under
concurrent growth/shrink — even inside holding_lock() — another writer's
just-appended data could be silently deleted.EmbedStore and DictStore (.b2d) now support cross-process writers
under locking: transactional writes plus key-map re-sync, so readers
follow keys added or removed by another process.DictStore.to_b2z() (and TreeStore, which inherits from it) now replaces
the target file atomically, so concurrent readers always see either the old
or the new archive, never a torn one.NDArray handle
opened before a resize() made through another handle follows the new
shape on its next data access, or via the new explicit NDArray.refresh().mmap_mode, Windows
in-use-file rename).detect_aligned_chunks() (used internally to fast-path aligned
slice reads/writes): a floor-division undercounted the chunk grid for
arrays whose shape isn't a multiple of the chunk shape, which could
silently return the wrong chunk's data for an otherwise-aligned slice
with a nonzero start in an earlier dimension.manylinux2014 (CentOS 7, glibc
2.17, GCC 10.2) to manylinux_2_28 (AlmaLinux 8, glibc 2.28, GCC 12),
fixing a build failure with NumPy >=2.5 which requires GCC >=10.3.NumPy 2.5 compatibility: adjusted for deprecations in NumPy 2.5.
CTable.sort_by(view=True): zero-copy sorted viewsCTable.sort_by() now accepts view=True, returning a lightweight
sorted view that shares the parent's column data and gathers rows on
demand in sorted order — no whole-table copy. This is ideal for reading a
sorted slice of a large (possibly on-disk) table::
t.sort_by("col", view=True)[:10] # top-10 without materialising
Sorting on a fully indexed column streams directly from the index, so the
table is never materialised. Multi-column sorts and dotted (nested) leaf
names are supported (e.g. t.sort_by(["trip.begin.lon", "payment.fare"], ascending=[True, False])).
where on dictionary (string) columnswhere expressions now work over dictionary-encoded (string) columns,
including membership tests such as '"Acme" in company', so categorical
text columns can be filtered without decoding the whole column.b2view is now an opt-in extrab2view terminal browser and its TUI stack (textual,
textual-plotext) are no longer core dependencies: a plain
pip install blosc2 no longer pulls them, keeping the compression library
lean (and dropping deps that are unusable under wasm32, which has no TTY).
Install the viewer with pip install "blosc2[tui]", or
pip install "blosc2[hires]" to also get the high-res h view. The
b2view command prints this hint if the dependencies are missing.group_by: flexible aggregation namingCTable.group_by(...).agg() now accepts a list of (column, ops) pairs
and explicit output names (pandas-style keyword arguments), alongside the
existing auto-suffixed mapping; the forms can be combined::
g.agg({"sales": ["sum", "mean"]}) # auto: sales_sum, sales_mean
g.agg([(t.sales, ["sum", "mean"])]) # auto, but accepts Column objects
g.agg(revenue=("sales", "sum")) # explicit: revenue
g.agg({"sales": "sum"}, n=("*", "size")) # combined, with a named row count
The list-of-pairs and named forms accept Column objects (t.sales), which
the mapping form cannot because Column is unhashable and so cannot be a dict
key.
Aggregation ops may also be given as the matching blosc2 reduction functions
(blosc2.sum, mean, min, max, argmin, argmax), matched by
identity -- e.g. g.agg([(t.sales, [blosc2.sum, "mean"])]). This is a
naming shorthand only; arbitrary/UDF callables (and look-alikes such as
np.sum or a user function named sum) are rejected rather than silently
misinterpreted.
group_by / group_reduce: tri-state sort=group_by() result building now
batch-decodes dictionary (string) keys in one pass (decode_batch) instead of
one decode() per group, making high-cardinality string group-bys dramatically
faster (end-to-end group_by().size() dropped from seconds to milliseconds on
~100k-group workloads).sort= is now a tri-state (None / True / False) on both
CTable.group_by() and blosc2.group_reduce():
True — always return groups sorted by key.False — never sort; deterministic but unspecified order.None (the new default) — auto: sort only when cheap. Integer and
dictionary keys are sorted (free / vectorized); float and multi-key results,
whose only ordering is an O(G log G) Python sort over every distinct group,
are left unsorted to avoid a cost that can rival the grouping itself on
high-cardinality data.CTable.group_by() previously returned results always sorted. Under the
new None default, float-key and multi-key group-bys are no longer
key-sorted by default — pass sort=True to restore sorted output. This is
a deliberate divergence from pandas (which defaults to sort=True), suited
to blosc2's large / on-disk datasets.blosc2.group_reduce() previously defaulted to sort=False (unsorted).
Under the new None default its cheap kernels now sort by default —
most visibly float keys, which previously came out in hash order. Integer
keys were already ascending; the generic Python fallback stays unsorted.
Pass sort=False to opt out.min/max on indexed Columns, and argmin/argmax inside group_by, are
now accelerated using the index's per-block min/max summaries: when an
index is available these reductions run from the precomputed summaries instead
of decompressing the underlying data, which is dramatically faster on large
columns. A fast path also builds min/max envelope plots from any index.group_by operation is memoized and reused when the same
grouping is requested again, avoiding recomputation in interactive / repeated
workflows (e.g. b2view).G): group a CTable by a column (integer, string,
or now float keys) directly in the viewer, with a three-list / two-column
menu; while grouped, S/R operate on the grouped result and the data
panel's subtitle shows a G(roup) chip. The last grouping is memoized for
instant reuse.S): sort a CTable by a fully indexed column via a
dropdown (R toggles reverse) as a zero-copy sort_by(view=True) that streams
from the index — the table is never materialised, Esc restores the original
order, and a SORTED chip shows in the status bar. Non-indexed columns can
now be sorted too. Sort and filter are mutually exclusive; a row window
composes over a sort, and an filter is preserved across Sort / Group.hi-res counterpart mirroring the line/scatter plots, and +/-
zoom about the view's left edge.--max maximizes the current panel, and escape is now the single,
consistent way to back out of every modal.st_mtime_ns, st_size) and cached index handles are
released when a table closes, so a file changed underneath an open handle is no
longer served stale.jit_backend="js")@blosc2.dsl_kernel kernels can now be transpiled
to JavaScript and run via the browser's JIT. It is the default there for
transpilable floating-point kernels (silently falling back to miniexpr for
anything it can't handle), and beats the WASM TinyCC JIT on compute-heavy
kernels (e.g. ~2.8x on a Newton-fractal kernel). Request it explicitly with
compute(jit_backend="js"); outside WebAssembly that raises._i0/_n0/_ndim/_flat_idx) and integer inputs
with a floating-point output. Integer/complex output, reductions, and
unsupported constructs stay on miniexpr. Native builds are unaffected.blosc2.validate_dsl_jit()A new introspection helper reports whether a DSL kernel actually JIT-compiles (vs. silently falling back to the interpreter) for a given set of operand and output dtypes, without running it on real data::
status = blosc2.validate_dsl_jit(kernel, [np.float64, np.float64], np.float64)
status["jit"] # True if a runtime JIT kernel was produced
DSLValidator now rejects ;-joined sibling statements with a clear
"one statement per line" error, and assigning to an input parameter raises a
targeted error naming the param.out, idx,
nitems, inputs or output clashed with codegen-internal identifiers,
causing the generated C to fail to compile and silently fall back to the
interpreter. Codegen identifiers are now namespaced under __me.This follow-up release builds the b2view terminal viewer into a richer data-exploration tool — a scatter plot, a searchable column picker, a one-shot
This follow-up release builds the b2view terminal viewer into a richer
data-exploration tool — a scatter plot, a searchable column picker, a
one-shot demo download, refreshed chrome, and several interaction fixes — and
upgrades the bundled C-Blosc2 to 3.1.4. WASM/Pyodide is now a fully
supported platform, and CTable.info reports per-column compressed sizes.
s to scatter the current column
(X) against another column (Y) chosen from a list, over the current (zoomed)
row range; h then opens a high-resolution matplotlib scatter.r key toggles between the min/max envelope and the raw
values (strided-sampled when the range is wide).c go-to-column key now opens a searchable,
selectable list (type to filter, ↑/↓, Enter) for CTables, instead of a text
field; N-D arrays still go by numeric index./ opens a searchable multi-select to pick which CTable
columns are displayed.b2view --download fetches a demo bundle
(chicago-taxi-flat.b2z by default) into the current directory if it is not
already there, then opens it.0payment.fare).r to restore (ESCAPE_TO_MINIMIZE = False).CTable.info shows per-column compressed sizes (cbytes and cratio),
and print_versions() uses clearer Python-Blosc2 / C-Blosc2 labels.CTable.sort_by(view=True): zero-copy sorted viewsCTable.sort_by() now accepts view=True, returning a lightweight
sorted view that shares the parent's column data and gathers rows on
demand in sorted order — no whole-table copy. This is ideal for reading a
sorted slice of a large (possibly on-disk) table::
t.sort_by("col", view=True)[:10] # top-10 without materialising
Sorting on a fully indexed column streams directly from the index, so the
table is never materialised. Multi-column sorts and dotted (nested) leaf
names are supported (e.g. t.sort_by(["trip.begin.lon", "payment.fare"], ascending=[True, False])).
where on dictionary (string) columnswhere expressions now work over dictionary-encoded (string) columns,
including membership tests such as '"Acme" in company', so categorical
text columns can be filtered without decoding the whole column.b2view is now an opt-in extrab2view terminal browser and its TUI stack (textual,
textual-plotext) are no longer core dependencies: a plain
pip install blosc2 no longer pulls them, keeping the compression library
lean (and dropping deps that are unusable under wasm32, which has no TTY).
Install the viewer with pip install "blosc2[tui]", or
pip install "blosc2[hires]" to also get the high-res h view. The
b2view command prints this hint if the dependencies are missing.group_by: flexible aggregation namingCTable.group_by(...).agg() now accepts a list of (column, ops) pairs
and explicit output names (pandas-style keyword arguments), alongside the
existing auto-suffixed mapping; the forms can be combined::
g.agg({"sales": ["sum", "mean"]}) # auto: sales_sum, sales_mean
g.agg([(t.sales, ["sum", "mean"])]) # auto, but accepts Column objects
g.agg(revenue=("sales", "sum")) # explicit: revenue
g.agg({"sales": "sum"}, n=("*", "size")) # combined, with a named row count
The list-of-pairs and named forms accept Column objects (t.sales), which
the mapping form cannot because Column is unhashable and so cannot be a dict
key.
Aggregation ops may also be given as the matching blosc2 reduction functions
(blosc2.sum, mean, min, max, argmin, argmax), matched by
identity -- e.g. g.agg([(t.sales, [blosc2.sum, "mean"])]). This is a
naming shorthand only; arbitrary/UDF callables (and look-alikes such as
np.sum or a user function named sum) are rejected rather than silently
misinterpreted.
group_by / group_reduce: tri-state sort=group_by() result building now
batch-decodes dictionary (string) keys in one pass (decode_batch) instead of
one decode() per group, making high-cardinality string group-bys dramatically
faster (end-to-end group_by().size() dropped from seconds to milliseconds on
~100k-group workloads).sort= is now a tri-state (None / True / False) on both
CTable.group_by() and blosc2.group_reduce():
True — always return groups sorted by key.False — never sort; deterministic but unspecified order.None (the new default) — auto: sort only when cheap. Integer and
dictionary keys are sorted (free / vectorized); float and multi-key results,
whose only ordering is an O(G log G) Python sort over every distinct group,
are left unsorted to avoid a cost that can rival the grouping itself on
high-cardinality data.CTable.group_by() previously returned results always sorted. Under the
new None default, float-key and multi-key group-bys are no longer
key-sorted by default — pass sort=True to restore sorted output. This is
a deliberate divergence from pandas (which defaults to sort=True), suited
to blosc2's large / on-disk datasets.blosc2.group_reduce() previously defaulted to sort=False (unsorted).
Under the new None default its cheap kernels now sort by default —
most visibly float keys, which previously came out in hash order. Integer
keys were already ascending; the generic Python fallback stays unsorted.
Pass sort=False to opt out.min/max on indexed Columns, and argmin/argmax inside group_by, are
now accelerated using the index's per-block min/max summaries: when an
index is available these reductions run from the precomputed summaries instead
of decompressing the underlying data, which is dramatically faster on large
columns. A fast path also builds min/max envelope plots from any index.group_by operation is memoized and reused when the same
grouping is requested again, avoiding recomputation in interactive / repeated
workflows (e.g. b2view).G): group a CTable by a column (integer, string,
or now float keys) directly in the viewer, with a three-list / two-column
menu; while grouped, S/R operate on the grouped result and the data
panel's subtitle shows a G(roup) chip. The last grouping is memoized for
instant reuse.S): sort a CTable by a fully indexed column via a
dropdown (R toggles reverse) as a zero-copy sort_by(view=True) that streams
from the index — the table is never materialised, Esc restores the original
order, and a SORTED chip shows in the status bar. Non-indexed columns can
now be sorted too. Sort and filter are mutually exclusive; a row window
composes over a sort, and an filter is preserved across Sort / Group.hi-res counterpart mirroring the line/scatter plots, and +/-
zoom about the view's left edge.--max maximizes the current panel, and escape is now the single,
consistent way to back out of every modal.st_mtime_ns, st_size) and cached index handles are
released when a table closes, so a file changed underneath an open handle is no
longer served stale.This release teaches the b2view terminal viewer to plot — peak-preserving envelope line plots of any series, with zoom, a row-window lock, and an opti
This release teaches the b2view terminal viewer to plot — peak-preserving
envelope line plots of any series, with zoom, a row-window lock, and an optional
high-resolution matplotlib view — and gives CTable a pandas-like display and
CSV experience. It also publishes WASM/Pyodide wheels to PyPI and adds
faster strided reads for NDArray and Column.
p on a numeric series (a CTable column or an
array row) to draw a braille line plot. Plots are peak-preserving min/max
envelopes by default, so no spike or trough is hidden however large the
series is; large local series stream their envelope exactly in bounded
spans (only remote c2arrays fall back to a labeled strided sample).v to lock the data grid to the plotted range so paging stays inside it
(escape unlocks). The plot and high-res views honor the locked window.h opens a high-res matplotlib image of the
plotted range (new optional hires extra: matplotlib + textual-image).enter decodes a single skipped/expensive CTable
cell, and SChunk nodes now preview as a paged hex dump.--path ... --panel data; status chips are branded yellow.CTable.to_string() now renders the whole table by default (every row and
every column), like pandas' DataFrame.to_string(). New max_rows and
max_width parameters truncate on demand. Behaviour change: previously
to_string() returned the truncated view; code that relied on that should
pass max_rows=/max_width= (or use str()).[N rows x M columns] dimensions footer now follows pandas: omitted by
to_string() (pass show_dimensions=True to force it), and shown by
str/repr/print only when the view is actually truncated. Previously it
was always appended.repr(ctable) now shows the same truncated table as str(ctable)
(pandas/polars convention), instead of the one-line CTable<…> summary. The
compact summary remains available via ctable.info.set_printoptions: display_width controls the
column-fitting width budget (None = auto-detect terminal, -1 = show all
columns, positive int = fixed budget), and display_rows now accepts -1 to
show all rows (0 still shows none).blosc2.printoptions(...) context manager temporarily sets the display
options and restores them on exit, e.g.
with blosc2.printoptions(display_rows=-1, display_width=-1): print(t).CTable.to_csv() now accepts no path, returning the CSV as a string like
pandas' DataFrame.to_csv(). Passing a path still writes the file (and
returns None); the returned string is byte-for-byte the same as the file.NDArray.__getitem__ gains a sparse-gather fast
path for large strides, and Column.__getitem__ short-circuits when the
logical positions equal the physical ones.step in Column getitem could return []; it now
returns the reversed selection.pyemscripten wheels for CPython 3.13 (2025 ABI) and 3.14 (2026 ABI) and
uploads them to PyPI, so blosc2 is micropip-installable in Pyodide, and
b2view prints a clear message instead of crashing when run under WASM.
Known limitation: slicing an in-memory SChunk loaded from a frame fails on
the Pyodide 0.29.x Emscripten toolchain (cp313); it works on Pyodide 314
(cp314) and natively. See issue #664.This follow-up release builds the b2view terminal viewer into a richer
data-exploration tool — a scatter plot, a searchable column picker, a
one-shot demo download, refreshed chrome, and several interaction fixes — and
upgrades the bundled C-Blosc2 to 3.1.4. WASM/Pyodide is now a fully
supported platform, and CTable.info reports per-column compressed sizes.
s to scatter the current column
(X) against another column (Y) chosen from a list, over the current (zoomed)
row range; h then opens a high-resolution matplotlib scatter.r key toggles between the min/max envelope and the raw
values (strided-sampled when the range is wide).c go-to-column key now opens a searchable,
selectable list (type to filter, ↑/↓, Enter) for CTables, instead of a text
field; N-D arrays still go by numeric index./ opens a searchable multi-select to pick which CTable
columns are displayed.b2view --download fetches a demo bundle
(chicago-taxi-flat.b2z by default) into the current directory if it is not
already there, then opens it.0payment.fare).r to restore (ESCAPE_TO_MINIMIZE = False).CTable.info shows per-column compressed sizes (cbytes and cratio),
and print_versions() uses clearer Python-Blosc2 / C-Blosc2 labels.Note: 4.4.4 was skipped due to a failure during the release process.
Note: 4.4.4 was skipped due to a failure during the release process.
This release promotes the b2view terminal viewer to a core feature —
installed by default, with new interactive row and column filtering — and
makes BatchArray block layouts (and hence compression ratios) reproducible
across CPUs.
textual and rich are now regular
dependencies, so the b2view CLI works out of the box (the [tui] extra
is gone). A getting-started walkthrough was added to the docs, and the
README now lists the CLI tools.f on a CTable node opens a modal that takes
the same string expressions as CTable.where() (dotted nested names,
and/or) and pages through the matching view. Filters are remembered
per node for the session, the data header shows the active filter plus
the unfiltered total, and escape (or an empty expression) clears it./ narrows the visible columns by case-insensitive
substring; column paging and the c goto-column modal then operate on
that subset. Combines freely with the row filter; escape clears one
layer per press (rows first, then columns).--mouse lets b2view
capture it instead (click-to-focus, wheel scrolling by half a page,
paging at the edges).? opens a help screen listing all keys; c jumps to a
column by index, exact name or unique name prefix; s/e jump to the
first/last column window; row paging and jumps keep the cursor on its
column; dim-mode index/viewport movements clamp at the boundaries instead
of wrapping around.tui), plus
render unit tests. Skipped on wasm, where Textual apps cannot start
(no termios).pip install . --group test (a PEP 735 dependency group); the stale
[test] extra syntax was removed.This release teaches the b2view terminal viewer to plot — peak-preserving
envelope line plots of any series, with zoom, a row-window lock, and an optional
high-resolution matplotlib view — and gives CTable a pandas-like display and
CSV experience. It also publishes WASM/Pyodide wheels to PyPI and adds
faster strided reads for NDArray and Column.
p on a numeric series (a CTable column or an
array row) to draw a braille line plot. Plots are peak-preserving min/max
envelopes by default, so no spike or trough is hidden however large the
series is; large local series stream their envelope exactly in bounded
spans (only remote c2arrays fall back to a labeled strided sample).v to lock the data grid to the plotted range so paging stays inside it
(escape unlocks). The plot and high-res views honor the locked window.h opens a high-res matplotlib image of the
plotted range (new optional hires extra: matplotlib + textual-image).enter decodes a single skipped/expensive CTable
cell, and SChunk nodes now preview as a paged hex dump.--path ... --panel data; status chips are branded yellow.CTable.to_string() now renders the whole table by default (every row and
every column), like pandas' DataFrame.to_string(). New max_rows and
max_width parameters truncate on demand. Behaviour change: previously
to_string() returned the truncated view; code that relied on that should
pass max_rows=/max_width= (or use str()).[N rows x M columns] dimensions footer now follows pandas: omitted by
to_string() (pass show_dimensions=True to force it), and shown by
str/repr/print only when the view is actually truncated. Previously it
was always appended.repr(ctable) now shows the same truncated table as str(ctable)
(pandas/polars convention), instead of the one-line CTable<…> summary. The
compact summary remains available via ctable.info.set_printoptions: display_width controls the
column-fitting width budget (None = auto-detect terminal, -1 = show all
columns, positive int = fixed budget), and display_rows now accepts -1 to
show all rows (0 still shows none).blosc2.printoptions(...) context manager temporarily sets the display
options and restores them on exit, e.g.
with blosc2.printoptions(display_rows=-1, display_width=-1): print(t).CTable.to_csv() now accepts no path, returning the CSV as a string like
pandas' DataFrame.to_csv(). Passing a path still writes the file (and
returns None); the returned string is byte-for-byte the same as the file.NDArray.__getitem__ gains a sparse-gather fast
path for large strides, and Column.__getitem__ short-circuits when the
logical positions equal the physical ones.step in Column getitem could return []; it now
returns the reversed selection.pyemscripten wheels for CPython 3.13 (2025 ABI) and 3.14 (2026 ABI) and
uploads them to PyPI, so blosc2 is micropip-installable in Pyodide, and
b2view prints a clear message instead of crashing when run under WASM.
Known limitation: slicing an in-memory SChunk loaded from a frame fails on
the Pyodide 0.29.x Emscripten toolchain (cp313); it works on Pyodide 314
(cp314) and natively. See issue #664.Nothing published for this version
This is a maintenance release focused on faster CTable cold-start, printing and groupby performance, a lighter import blosc2, new raw-storage access f
This is a maintenance release focused on faster CTable cold-start, printing
and groupby performance, a lighter import blosc2, new raw-storage access
for columns, and support for the new J2K/HTJ2K codec plugins.
select() (and other view-producing
operations) no longer open every projected column up front. A column is
only opened from storage when the view actually reads it, so selecting and
then touching a subset of columns — or aggregating a single one — skips
the cold-start cost of the rest.repr()/to_string() now memoise per-column
sparse gathers for the duration of a render and combine the head and tail
rows into a single sparse read per column. Each column is read from
storage once instead of ~6 times (precision detection, width sizing and
row rendering all hit the cache)..b2z/.b2d store in 'r' mode
no longer creates a temporary working directory, since nothing is ever
written.concurrent.futures
instead of an asyncio event loop. import blosc2 no longer pulls in
~30 asyncio modules, saving ~3 MB of memory footprint at import time.thread.join() while the
reader thread was stuck on a full prefetch queue. A stop event now makes
the producer bail out when its consumer goes away.Column.raw accessor: returns the underlying storage container of a
column (NDArray, ListArray, DictionaryColumn, …) directly. Unlike
Column.__getitem__, which always materializes NumPy arrays, this is the
column as a blosc2-native compressed object — usable as a lazy-expression
operand without decompressing, and exposing storage details like schunk,
chunks or cparams. Note that this is a physical view: fixed-width
containers are over-allocated to chunk capacity, so slice to len(table)
to get just the live rows, and no validity-mask or null-sentinel
processing is applied. Raises AttributeError for computed columns,
which have no backing storage.blosc2.Codec.J2K and blosc2.Codec.HTJ2K
expose the IDs for the new JPEG 2000 codec plugins (installable with
pip install blosc2-j2k and pip install blosc2-htj2k).--float-trunc-prec and nested columns: the precision-truncation
filter of the parquet_to_blosc2 CLI now propagates to float fields
inside nested (struct/list) columns too.ValueError at add_computed_column() time,
instead of silently breaking on reload.Note: 4.4.4 was skipped due to a failure during the release process.
This release promotes the b2view terminal viewer to a core feature —
installed by default, with new interactive row and column filtering — and
makes BatchArray block layouts (and hence compression ratios) reproducible
across CPUs.
textual and rich are now regular
dependencies, so the b2view CLI works out of the box (the [tui] extra
is gone). A getting-started walkthrough was added to the docs, and the
README now lists the CLI tools.f on a CTable node opens a modal that takes
the same string expressions as CTable.where() (dotted nested names,
and/or) and pages through the matching view. Filters are remembered
per node for the session, the data header shows the active filter plus
the unfiltered total, and escape (or an empty expression) clears it./ narrows the visible columns by case-insensitive
substring; column paging and the c goto-column modal then operate on
that subset. Combines freely with the row filter; escape clears one
layer per press (rows first, then columns).--mouse lets b2view
capture it instead (click-to-focus, wheel scrolling by half a page,
paging at the edges).? opens a help screen listing all keys; c jumps to a
column by index, exact name or unique name prefix; s/e jump to the
first/last column window; row paging and jumps keep the cursor on its
column; dim-mode index/viewport movements clamp at the boundaries instead
of wrapping around.tui), plus
render unit tests. Skipped on wasm, where Textual apps cannot start
(no termios).pip install . --group test (a PEP 735 dependency group); the stale
[test] extra syntax was removed.This is a feature and maintenance release that promotes DSL kernels to first-class CTable computed columns, adds a new CTable.__setitem__ assignment i
This is a feature and maintenance release that promotes DSL kernels to
first-class CTable computed columns, adds a new CTable.__setitem__
assignment idiom, optimises bulk NDArray writes, and fixes several
correctness issues.
add_computed_column() accepts DSL kernels: @blosc2.dsl_kernel-decorated
functions can now back virtual computed columns directly, in addition to
the existing string-expression form. The column survives save/open
round-trips via persisted dsl_source.add_generated_column() accepts DSL kernels: stored generated columns
(written during append/extend and on refresh_generated_column())
now support DSL kernels as their transformer.CTable.where() accepts UDF/DSL kernels: filter predicates are no
longer limited to expression strings — any DSL kernel can be passed directly.dtype inference for DSL kernels: when dtype is omitted,
lazyudf() infers the output dtype via NumPy type promotion of the input
column dtypes. Pass dtype explicitly for type-changing kernels
(comparisons, casts).kernel_from_source() utility: new dsl_kernel.kernel_from_source()
reconstructs a DSLKernel from its stored source text, shared by the
CTable DSL-column loaders and the persisted LazyUDF decoder..b2d files from untrusted sources that contain DSL
computed columns execute stored Python source on open. A warning is now
included in the documentation.CTable.__setitem__ column-assignment APIt["col"] = arr: new shorthand equivalent to t["col"][:] = arr.
Accepts any array-like including blosc2.NDArray. Raises KeyError for
unknown columns and ValueError for views or read-only tables.extend() and Column.__setitem__extend({"col": ndarray}) decompresses chunk-by-chunk: when a
blosc2.NDArray is passed as a column value to extend(), it is now
written in chunks instead of being fully decompressed upfront. Pass
validate=False to avoid a transient full decompression during constraint
checking.col[:] = blosc2_ndarray fast path: a new no-holes fast path in
Column.__setitem__ skips the O(n) validity-mask gather and writes the
NDArray one chunk at a time using contiguous slice writes. Works for both
scalar and fixed-shape ndarray columns. Falls back to a chunked fancy-index
path when deleted rows are present.BLOSC_ME_JIT environment variable overrideBLOSC_ME_JIT now takes unconditional priority over
both the jit= and jit_backend= keyword arguments, making it easy to
switch JIT backends from the command line without modifying code.Column.__setitem__: a None == None guard
evaluation on view-backed columns could fire the NDArray fast path,
bypassing physical-position remapping and silently corrupting rows. Fixed
by explicitly checking base is None before activating the fast path.CTable.__setitem__ view guard: the new t["col"] = arr API now
raises ValueError on views, matching the contract of all other mutating
CTable methods._last_pos starts as
None. The guard now calls _resolve_last_pos() to lazily initialise it.jit_backend preserved in _empty_copy: the jit_backend
setting was silently dropped during internal table copies; it is now
retained.lazyexpr Column unwrapping: convert_inputs() now automatically
unwraps CTable.Column objects to their backing NDArray so that shape and
identity checks work correctly.ndarray.py and
ctable.py now use blosc2.array(), blosc2.arange(), and
blosc2.linspace() directly instead of two-step numpy-then-asarray
patterns.udf-computed-col.py example: new end-to-end example demonstrating DSL
kernel computed and generated columns.This is a maintenance release focused on faster CTable cold-start, printing
and groupby performance, a lighter import blosc2, new raw-storage access
for columns, and support for the new J2K/HTJ2K codec plugins.
select() (and other view-producing
operations) no longer open every projected column up front. A column is
only opened from storage when the view actually reads it, so selecting and
then touching a subset of columns — or aggregating a single one — skips
the cold-start cost of the rest.repr()/to_string() now memoise per-column
sparse gathers for the duration of a render and combine the head and tail
rows into a single sparse read per column. Each column is read from
storage once instead of ~6 times (precision detection, width sizing and
row rendering all hit the cache)..b2z/.b2d store in 'r' mode
no longer creates a temporary working directory, since nothing is ever
written.concurrent.futures
instead of an asyncio event loop. import blosc2 no longer pulls in
~30 asyncio modules, saving ~3 MB of memory footprint at import time.thread.join() while the
reader thread was stuck on a full prefetch queue. A stop event now makes
the producer bail out when its consumer goes away.Column.raw accessor: returns the underlying storage container of a
column (NDArray, ListArray, DictionaryColumn, …) directly. Unlike
Column.__getitem__, which always materializes NumPy arrays, this is the
column as a blosc2-native compressed object — usable as a lazy-expression
operand without decompressing, and exposing storage details like schunk,
chunks or cparams. Note that this is a physical view: fixed-width
containers are over-allocated to chunk capacity, so slice to len(table)
to get just the live rows, and no validity-mask or null-sentinel
processing is applied. Raises AttributeError for computed columns,
which have no backing storage.blosc2.Codec.J2K and blosc2.Codec.HTJ2K
expose the IDs for the new JPEG 2000 codec plugins (installable with
pip install blosc2-j2k and pip install blosc2-htj2k).--float-trunc-prec and nested columns: the precision-truncation
filter of the parquet_to_blosc2 CLI now propagates to float fields
inside nested (struct/list) columns too.ValueError at add_computed_column() time,
instead of silently breaking on reload.This is a feature release focused on a new interactive data viewer, automatic SUMMARY indexes for fast WHERE queries, chunk-aligned Arrow/Parquet impo
This is a feature release focused on a new interactive data viewer, automatic
SUMMARY indexes for fast WHERE queries, chunk-aligned Arrow/Parquet imports,
expanded where() acceleration via miniexpr, and a range of CTable ergonomics
and performance improvements. Python 3.10 support has been dropped; Python
3.11 is now the minimum.
b2view command: a terminal-based interactive viewer for all
blosc2 containers — NDArray, CTable, SChunk, BatchArray, and more.
Launch it with b2view <file> or as blosc2.b2view() from Python.t/b
jump to the top/bottom; --panel jumps straight to a named panel on launch.CTable.vlmeta panel: variable-length metadata is exposed in a dedicated
panel.CTable is closed after a
write session, SUMMARY indexes (per-block min/max) are built by default for
all eligible scalar columns with no extra configuration needed.extend() and Arrow import, so closing the table costs almost nothing
beyond the write already done.--no-summary-index: new CLI flag for parquet-to-blosc2 to disable
automatic index creation on import.--reduce-mem: new CLI option for parquet-to-blosc2 to cap the Arrow
read-batch size on nested list<struct> imports, keeping peak RSS low at a
modest speed cost.ListArray and BatchArray: a new chunk_copy()
method transfers pre-compressed chunks directly at the C level, bypassing
Python-level serialization and recompression. CTable.copy() uses this path
automatically.chunks= / blocks= overrides in CTable.copy(): callers can now
specify target chunk and block sizes for the output copy.cparams and blocks overrides: CTable.copy() accepts cparams and
blocks to recompress the copy with different settings.--chunks / --blocks added to the parquet-to-blosc2 CLI.NDArray.take() following Array API take shape semantics, including
axis=None flattening and N-dimensional integer indices. One-dimensional
gathers use a new sparse C-level path (b2nd_get_sparse_cbuffer) internally.blosc2.take() to dispatch to NDArray.take(),
CTable.take(), and Column.take() while preserving the input container
type.CTable.take() and Column.take() for logical row/value gathers that
preserve order and duplicate indices, unlike mask-based views.ndim > 1 axis-based take, orthogonal selection is used internally for
better performance.where(cond, x) via miniexpr: the single-argument where (fill-with-zero
variant) is now handled directly by the miniexpr engine when the condition is
a boolean array, avoiding a numexpr round-trip.where(cond, x, y) via miniexpr: the two-argument flavor is likewise
dispatched to miniexpr for element-wise conditional selection.NDArray.__getitem__ with a boolean array key
now detects it before the general process_key / nonzero path, avoiding
wasted work.BLOSC_ME_JIT / BLOSC_ME_JIT_TRACE: new environment variables to
control and trace the miniexpr JIT backend at runtime.sort_by() on a view is now lazy: calling sort_by() on a filtered view
returns a position-reordered view without materializing data; the sort
positions are cached and used directly on column access.select() on a view no
longer materializes unneeded columns eagerly; columns are resolved only when
accessed.NestedColumn public class: the previously internal
_NestedColumnNamespace has been renamed and promoted to NestedColumn,
providing aggregate metadata (col_names, nrows, nbytes, cbytes,
cratio) and a structured .info report over a group of dotted columns..info across containers: Column.info, CTable.info,
NestedColumn.info, and related classes now follow a consistent field order
(identity → shape/grid → sizes → content → compression params).blosc2.open() — NDArray, SChunk, CTable,
BatchArray, ListArray, and stores — now support the with statement.
The __exit__ method flushes and closes the underlying storage..b2z
archives are now read in-place rather than extracted to a temporary directory,
cutting open latency for indexed tables.iterchunks_info(): several hot loops switched to
iterchunks_info() for lower overhead per chunk.DictStore embed disabled by default: embedding a store inside a dict
store is now opt-in (it was error-prone as the default).compute_chunks_blocks now
guarantees chunk dimensions are capped at the array shape dimension.max_rows robust to older PyArrow: truncation logic no longer depends on
PyArrow APIs that are absent in older releases.cratio display: compression ratio is now shown with an explicit x
suffix (e.g. 2.47x) throughout .info output.This is a feature and maintenance release that promotes DSL kernels to
first-class CTable computed columns, adds a new CTable.__setitem__
assignment idiom, optimises bulk NDArray writes, and fixes several
correctness issues.
add_computed_column() accepts DSL kernels: @blosc2.dsl_kernel-decorated
functions can now back virtual computed columns directly, in addition to
the existing string-expression form. The column survives save/open
round-trips via persisted dsl_source.add_generated_column() accepts DSL kernels: stored generated columns
(written during append/extend and on refresh_generated_column())
now support DSL kernels as their transformer.CTable.where() accepts UDF/DSL kernels: filter predicates are no
longer limited to expression strings — any DSL kernel can be passed directly.dtype inference for DSL kernels: when dtype is omitted,
lazyudf() infers the output dtype via NumPy type promotion of the input
column dtypes. Pass dtype explicitly for type-changing kernels
(comparisons, casts).kernel_from_source() utility: new dsl_kernel.kernel_from_source()
reconstructs a DSLKernel from its stored source text, shared by the
CTable DSL-column loaders and the persisted LazyUDF decoder..b2d files from untrusted sources that contain DSL
computed columns execute stored Python source on open. A warning is now
included in the documentation.CTable.__setitem__ column-assignment APIt["col"] = arr: new shorthand equivalent to t["col"][:] = arr.
Accepts any array-like including blosc2.NDArray. Raises KeyError for
unknown columns and ValueError for views or read-only tables.extend() and Column.__setitem__extend({"col": ndarray}) decompresses chunk-by-chunk: when a
blosc2.NDArray is passed as a column value to extend(), it is now
written in chunks instead of being fully decompressed upfront. Pass
validate=False to avoid a transient full decompression during constraint
checking.col[:] = blosc2_ndarray fast path: a new no-holes fast path in
Column.__setitem__ skips the O(n) validity-mask gather and writes the
NDArray one chunk at a time using contiguous slice writes. Works for both
scalar and fixed-shape ndarray columns. Falls back to a chunked fancy-index
path when deleted rows are present.BLOSC_ME_JIT environment variable overrideBLOSC_ME_JIT now takes unconditional priority over
both the jit= and jit_backend= keyword arguments, making it easy to
switch JIT backends from the command line without modifying code.Column.__setitem__: a None == None guard
evaluation on view-backed columns could fire the NDArray fast path,
bypassing physical-position remapping and silently corrupting rows. Fixed
by explicitly checking base is None before activating the fast path.CTable.__setitem__ view guard: the new t["col"] = arr API now
raises ValueError on views, matching the contract of all other mutating
CTable methods._last_pos starts as
None. The guard now calls _resolve_last_pos() to lazily initialise it.jit_backend preserved in _empty_copy: the jit_backend
setting was silently dropped during internal table copies; it is now
retained.lazyexpr Column unwrapping: convert_inputs() now automatically
unwraps CTable.Column objects to their backing NDArray so that shape and
identity checks work correctly.ndarray.py and
ctable.py now use blosc2.array(), blosc2.arange(), and
blosc2.linspace() directly instead of two-step numpy-then-asarray
patterns.udf-computed-col.py example: new end-to-end example demonstrating DSL
kernel computed and generated columns.note: 4.3.2 was an internal pre-release that was not published to PyPI.
note: 4.3.2 was an internal pre-release that was not published to PyPI.
This is a maintenance release focused on CTable display ergonomics, indexed-query correctness, and query-planner performance.
str(table) / print(table) now use
a compact, pandas/DuckDB-style table representation, including a displayed
logical row index, numeric alignment, compact spacing, and a trailing footer
such as [726017 rows x 5 columns].blosc2.set_printoptions() and
blosc2.get_printoptions() for CTable rendering. The supported options are
display_index, display_rows, display_precision, and fancy.CTable.to_string(): added a one-off formatting API for producing CTable
string representations without changing global print options.display_rows threshold, only the first five and last five rows are shown,
with an ellipsis row in between.set_printoptions(fancy=True) restores the more
decorated display with dtype rows, separator rules, and hidden row/column
counts.FULL, PARTIAL, or OPSI)
as a compact pre-filter, then refine the remaining predicates on those
positions instead of scanning the full table.NaN values, so indexed results match scan
results for bucket/full index lookups.valid_rows in several CTable code paths.nrows calls in the
query planner.heavy to reduce default test-suite
runtime.ListArray and CTable
sections, including CTable's columnar storage model and support for columns
backed by NDArray, BatchArray, ObjectArray, ListArray, and related
containers.This is a feature release focused on a new interactive data viewer, automatic
SUMMARY indexes for fast WHERE queries, chunk-aligned Arrow/Parquet imports,
expanded where() acceleration via miniexpr, and a range of CTable ergonomics
and performance improvements. Python 3.10 support has been dropped; Python
3.11 is now the minimum.
b2view command: a terminal-based interactive viewer for all
blosc2 containers — NDArray, CTable, SChunk, BatchArray, and more.
Launch it with b2view <file> or as blosc2.b2view() from Python.t/b
jump to the top/bottom; --panel jumps straight to a named panel on launch.CTable.vlmeta panel: variable-length metadata is exposed in a dedicated
panel.CTable is closed after a
write session, SUMMARY indexes (per-block min/max) are built by default for
all eligible scalar columns with no extra configuration needed.extend() and Arrow import, so closing the table costs almost nothing
beyond the write already done.--no-summary-index: new CLI flag for parquet-to-blosc2 to disable
automatic index creation on import.--reduce-mem: new CLI option for parquet-to-blosc2 to cap the Arrow
read-batch size on nested list<struct> imports, keeping peak RSS low at a
modest speed cost.ListArray and BatchArray: a new chunk_copy()
method transfers pre-compressed chunks directly at the C level, bypassing
Python-level serialization and recompression. CTable.copy() uses this path
automatically.chunks= / blocks= overrides in CTable.copy(): callers can now
specify target chunk and block sizes for the output copy.cparams and blocks overrides: CTable.copy() accepts cparams and
blocks to recompress the copy with different settings.--chunks / --blocks added to the parquet-to-blosc2 CLI.NDArray.take() following Array API take shape semantics, including
axis=None flattening and N-dimensional integer indices. One-dimensional
gathers use a new sparse C-level path (b2nd_get_sparse_cbuffer) internally.blosc2.take() to dispatch to NDArray.take(),
CTable.take(), and Column.take() while preserving the input container
type.CTable.take() and Column.take() for logical row/value gathers that
preserve order and duplicate indices, unlike mask-based views.ndim > 1 axis-based take, orthogonal selection is used internally for
better performance.where(cond, x) via miniexpr: the single-argument where (fill-with-zero
variant) is now handled directly by the miniexpr engine when the condition is
a boolean array, avoiding a numexpr round-trip.where(cond, x, y) via miniexpr: the two-argument flavor is likewise
dispatched to miniexpr for element-wise conditional selection.NDArray.__getitem__ with a boolean array key
now detects it before the general process_key / nonzero path, avoiding
wasted work.BLOSC_ME_JIT / BLOSC_ME_JIT_TRACE: new environment variables to
control and trace the miniexpr JIT backend at runtime.sort_by() on a view is now lazy: calling sort_by() on a filtered view
returns a position-reordered view without materializing data; the sort
positions are cached and used directly on column access.select() on a view no
longer materializes unneeded columns eagerly; columns are resolved only when
accessed.NestedColumn public class: the previously internal
_NestedColumnNamespace has been renamed and promoted to NestedColumn,
providing aggregate metadata (col_names, nrows, nbytes, cbytes,
cratio) and a structured .info report over a group of dotted columns..info across containers: Column.info, CTable.info,
NestedColumn.info, and related classes now follow a consistent field order
(identity → shape/grid → sizes → content → compression params).blosc2.open() — NDArray, SChunk, CTable,
BatchArray, ListArray, and stores — now support the with statement.
The __exit__ method flushes and closes the underlying storage..b2z
archives are now read in-place rather than extracted to a temporary directory,
cutting open latency for indexed tables.iterchunks_info(): several hot loops switched to
iterchunks_info() for lower overhead per chunk.DictStore embed disabled by default: embedding a store inside a dict
store is now opt-in (it was error-prone as the default).compute_chunks_blocks now
guarantees chunk dimensions are capped at the array shape dimension.max_rows robust to older PyArrow: truncation logic no longer depends on
PyArrow APIs that are absent in older releases.cratio display: compression ratio is now shown with an explicit x
suffix (e.g. 2.47x) throughout .info output.This is a maintenance release focused on CTable nested-column ergonomics, grouped reductions, and API/documentation polish.
This is a maintenance release focused on CTable nested-column ergonomics, grouped reductions, and API/documentation polish.
group_by() results: grouped output columns can now
preserve dotted/nested names such as trip.sec instead of requiring valid
Python identifiers.CTable.group_by() and CTable.sort_by() now
accept Column objects as well as string names, enabling idioms such as
t.group_by(t.trip.sec) and t.sort_by(t.trip.sec).CTableGroupBy now supports argmin() and
argmax(), plus agg({"col": "argmin"}) / agg({"col": "argmax"}).
Results are logical row positions in the grouped table or view; groups with no
non-null values return -1.blosc2.array(): added a NumPy-like constructor for NDArrays. It mirrors
blosc2.asarray() but defaults to copy=True, so passing an existing
NDArray creates a copy unless copy=False or copy=None is requested.RowTransformer, Column.row_transformer,
and CTableGroupBy.argmin / argmax documentation.blosc2.ndarray(), blosc2.dictionary(), and related public schema
factory functions to the Schema Specs reference.blosc2.group_reduce() into the Reduction Functions reference and
updated its example to use Blosc2 NDArrays.note: 4.3.2 was an internal pre-release that was not published to PyPI.
This is a maintenance release focused on CTable display ergonomics, indexed-query correctness, and query-planner performance.
str(table) / print(table) now use
a compact, pandas/DuckDB-style table representation, including a displayed
logical row index, numeric alignment, compact spacing, and a trailing footer
such as [726017 rows x 5 columns].blosc2.set_printoptions() and
blosc2.get_printoptions() for CTable rendering. The supported options are
display_index, display_rows, display_precision, and fancy.CTable.to_string(): added a one-off formatting API for producing CTable
string representations without changing global print options.display_rows threshold, only the first five and last five rows are shown,
with an ellipsis row in between.set_printoptions(fancy=True) restores the more
decorated display with dtype rows, separator rules, and hidden row/column
counts.FULL, PARTIAL, or OPSI)
as a compact pre-filter, then refine the remaining predicates on those
positions instead of scanning the full table.NaN values, so indexed results match scan
results for bucket/full index lookups.valid_rows in several CTable code paths.nrows calls in the
query planner.heavy to reduce default test-suite
runtime.ListArray and CTable
sections, including CTable's columnar storage model and support for columns
backed by NDArray, BatchArray, ObjectArray, ListArray, and related
containers.Multidimensional columns: CTable columns can now hold NDArray-backed cells, allowing each row of a column to contain a full n-dimensional compressed a
## Changes from 4.2.0 to 4.3.0
### CTable: N-dimensional (ndarray) columns
Multidimensional columns: CTable columns can now hold NDArray-backed cells, allowing each row of a column to contain a full n-dimensional compressed array. This enables use cases such as embedding vectors, image patches, time-series windows, or any other multidimensional per-row payload.
CSV and DataFrame import/export: Multidimensional column data can be imported and exported via CSV and pandas DataFrames, with automatic detection of array-valued cells.
Nullable ndarray columns: Multidimensional columns fully support the nullable semantics (null_count, sentinel handling, null_policy) already available for scalar columns.
`from_pandas()` improvements: CTable.from_pandas() now creates the correct specialized backing storage for DictionarySpec, ListSpec, VLStringSpec, VLBytesSpec, and other variable-length scalar specifications.
Improved schema coverage: New CTable timestamp schema type and extended Column.info output with shape, chunks, and blocks descriptors.
Arg reductions: Added argmin() and argmax() for scalar and ndarray CTable columns, plus row-transformer support for generated columns such as per-row peak-hour or dominant-embedding-dimension features.
### CTable: Group-by and filtered aggregation
`CTable.group_by()`: The primary group-by interface. Call t.group_by("city", sort=True).agg({"qty": "mean"}) to produce a new CTable with aggregated results. Single-key and multi-key groupings are supported, along with convenience methods such as .size(), .count(), .sum(), .mean(), .min() and .max():
by_city = t.group_by("city", sort=True)
by_city.size() # COUNT(*)
by_city.sum("sales") # SUM(sales) per city
by_city.agg({"sales": ["sum", "mean"]}) # SUM(sales), AVG(sales) per city
Performance accelerators: Dedicated Cython fast paths deliver significant speedups: ~25× for float32/64 group-by keys, ~8× for integer and dictionary-code keys, and a general-purpose hash table for arbitrary float keys.
Filtered aggregate pushdown: The where= parameter is now accepted in aggregation methods, pushing the filter into the compute engine so that only matching rows are read and reduced.
Persistent grouped output: Group-by results can be saved directly to persistent storage via the urlpath= parameter.
`blosc2.group_reduce()`: New public function that performs group-by reduction over NDArray instances and CTable columns, with Cython-accelerated backends for common key/reduction combinations.
### CTable: Dictionary / categorical columns
`DictionarySpec` column type: Introduced a new dictionary-encoded (categorical) column type that stores string or integer codes mapped to a shared dictionary, providing compact storage and accelerated equality and membership queries.
Dictionary types in `where` clauses: Dictionary columns can be queried with the same where= expression syntax as other column types, including nested dotted-name access.
Improved display: CTable printing now adapts to the terminal width, and dictionary values are shown in their decoded form. Column.info has been extended with type details, shape, chunks, and blocks.
### CTable: Nested columns and field-name escaping
Dotted nested column access: Columns whose names contain literal . (e.g., "root.nested") are now fully addressable via the dotted accessor syntax in where expressions, __getitem__, and the public API.
Hierarchical `_cols` storage paths: The internal column storage layout now preserves a hierarchical structure that mirrors the logical nesting, improving introspection and interop.
Nested-field pipeline: A new flattened-storage pipeline with logical mapping preserves nested schema structure (field names, types, and hierarchy) through Arrow and Parquet import/export. For unnamed top-level list<struct<...>> Parquet files, the logical schema round-trips faithfully, though the original physical row grouping is intentionally not preserved.
Field-name escaping: Special characters (. and /) in column names are automatically escaped during schema construction and metadata round-trips.
### Parquet import/export improvements
Arrow serializer by default: CTable.from_parquet() now defaults to the Arrow serializer, providing better schema fidelity and nested-type support.
Progress reporting: A --progress flag and an ETA estimator have been added to the parquet-to-blosc2 CLI for long-running imports.
`--max-rows` parameter: CTable.from_parquet() and the CLI now accept max_rows to limit the number of imported rows.
`--timestamp-unit`: New CLI option to control timestamp unit conversion on import.
`--float-trunc-prec`: New CLI option to truncate floating-point precision on import.
Separated nested columns enabled by default: The separate_nested_cols flag is now True by default for both the Python API and the CLI, ensuring nested Arrow structs are always expanded into flat columns.
`list_serializer` parameter: New option to control how list-type columns are serialized, with sensible defaults for different list layouts.
Validation optimizations: Arrow datetime values are validated only during import, reducing runtime overhead on subsequent operations.
### TreeStore: Inline CTable support
CTables inside TreeStore: CTable objects can now be stored inline as items inside a TreeStore, enabling hierarchical storage that mixes arrays and tables in a single persistent container.
Cache hardening: TreeStore cache assignments now use defensive copies and cache effective object roots to avoid aliasing and stale-cache errors.
Examples and tutorials: New tutorials and docstring examples demonstrate how to store, retrieve, and query CTables within a TreeStore.
### Performance and usability enhancements
Faster open and import: blosc2.open() and store constructors now assume valid file extensions and defer column metainfo loading, making CTable.open() and package import noticeably faster.
`CTable.nrows` is now lazy: The row count is computed on demand rather than eagerly, speeding up open and schema-inspection workflows.
Accelerated scalar and small-slice access: The batch/list path for reading scalar values or small column slices has been overhauled, eliminating internal placeholder materialization and yielding lower latency.
Late-import optimizations: Heavy optional dependencies are imported lazily at the blosc2 package level, reducing the baseline import blosc2 overhead.
`iter_arrow_batches()` optimization: Avoids full Python object materialization of batches during iteration, reducing memory pressure.
`NDArray`-to-list conversion: Small optimization when converting NDArray objects to Python lists.
`_last_pos` invalidation skipped: Mid-table deletes no longer eagerly invalidate cached positional state, improving delete latency.
### Documentation, examples and benchmarks
API reference expanded: blosc2.group_reduce() has been added to the Sphinx reference, along with updated CTable, Column, and TreeStore pages.
New tutorials and examples: Added sections on CTable–TreeStore integration, nested fields, dictionary columns, aggregates, grouping and querying with where=.
New benchmarks: Graph benchmarks for CTable insert time, column count, memory usage, and where= queries, plus dedicated group-by, nested-filter, and Parquet round-trip benchmarks.
### Fixes and compatibility
Null and NaN handling: NumPy scalar null sentinels are now normalized to plain Python scalars, and floating-point NaN sentinels are treated consistently with Python float('nan').
Empty aggregate results: Filtered aggregations that produce no rows now handle the empty result gracefully.
Generated column safety: Accessing a stalled (unfillable) generated column now raises a clear exception instead of producing undefined results.
Miniexpr bundling: Miniexpr’s bundled libtcc and related runtime files are now kept inside the blosc2 package, avoiding conflicts with other TCC installations.
Test improvements: Torch-dependent tests are marked as heavy, PyArrow-optional tests are skipped when the library is absent, and parametrization matrices have been trimmed to reduce CI time.
Missing Cython validation: Added validation guards for several Cython extension functions that previously lacked explicit error checking.
C-Blosc2 update: Bundled C-Blosc2 has been updated to the latest version (3.0.3).
``blosc2.open()`` default mode changed from 'a' to 'r': Removed the FutureWarning that was added to prepare for this transition.
This is a maintenance release focused on CTable nested-column ergonomics, grouped reductions, and API/documentation polish.
group_by() results: grouped output columns can now
preserve dotted/nested names such as trip.sec instead of requiring valid
Python identifiers.CTable.group_by() and CTable.sort_by() now
accept Column objects as well as string names, enabling idioms such as
t.group_by(t.trip.sec) and t.sort_by(t.trip.sec).CTableGroupBy now supports argmin() and
argmax(), plus agg({"col": "argmin"}) / agg({"col": "argmax"}).
Results are logical row positions in the grouped table or view; groups with no
non-null values return -1.blosc2.array(): added a NumPy-like constructor for NDArrays. It mirrors
blosc2.asarray() but defaults to copy=True, so passing an existing
NDArray creates a copy unless copy=False or copy=None is requested.RowTransformer, Column.row_transformer,
and CTableGroupBy.argmin / argmax documentation.blosc2.ndarray(), blosc2.dictionary(), and related public schema
factory functions to the Schema Specs reference.blosc2.group_reduce() into the Reduction Functions reference and
updated its example to use Blosc2 NDArrays.Introduced blosc2.CTable, a new columnar table container for compressed, typed columns. CTables support dataclass- and schema-based construction, row
blosc2.CTable, a new columnar table container for compressed, typed columns. CTables support dataclass- and schema-based construction, row iteration, column access, table views, head() / tail() / sample(), sorting, selection and compact where expressions.TreeStore, with support for blosc2.open(), CTable.open(), CTable.load(), CTable.save(), CTable.to_b2d() and CTable.to_b2z(). CTable views can be saved too, and .b2z/.b2d path handling has been tightened.append(), extend(), delete(), compact(), add_column(), drop_column(), rename_column() and related schema validation.LazyExpr/NDArray masks in CTable.__getitem__, iter_sorted() and indexing support for .b2z tables.ListArray / ObjectArray, including vlstring and vlbytes schema specs, fixed-length string/bytes import support and list/struct Arrow/Parquet round-trips.CTable.from_arrow_batches() improvements and a new parquet-to-blosc2 CLI utility.blosc2.Index as the unified public index handle, plus APIs such as create_index(), compact_index(), iter_sorted(), will_use_index() and related query explanation support.blosc2.argsort() and refactored indexing APIs around explicit index enums and sorting helpers.tmpdir support for full out-of-core indexes.C2Array, LazyExpr and DSL LazyUDF objects.blosc2.Ref for serializing external references, plus examples for b2object bundles and persisted expressions/UDFs.blosc2.load() as a convenience loader.vlmeta support to LazyArray objects.DictStore, allowing reopened proxies to refill caches after read-only opens, relaxing DictStore/TreeStore suffix requirements and adding DictStore.to_b2d().blosc2.open() by trying standard opens first and warning on implicit append mode.ObjectArray for fully general object data and renamed the earlier VLArray work accordingly; added ListArray docstrings and Arrow integration improvements.blosc2.struct() and blosc2.object() for nested/fully general column declarations.fromiter() with direct chunked construction and substantially lower peak memory use.asarray() behavior for NDArray inputs when copy-inducing keyword arguments are supplied.SChunk.reorder_offsets().BatchArray defaults and documentation; the default compression level is now tuned for faster lookup/scan behavior.parquet-to-blosc2 command with options such as --max-rows, --parquet-batch-size, --blosc2-items-per-block and --use-dict./tmp..b2z double-open corruption caused by GC-triggered repacking and made temporary .b2z unpacking default to the source file directory.arange() regressions and several pre-existing set_slice error-handling issues.### CTable: N-dimensional (ndarray) columns
Multidimensional columns: CTable columns can now hold NDArray-backed cells, allowing each row of a column to contain a full n-dimensional compressed array. This enables use cases such as embedding vectors, image patches, time-series windows, or any other multidimensional per-row payload.
CSV and DataFrame import/export: Multidimensional column data can be imported and exported via CSV and pandas DataFrames, with automatic detection of array-valued cells.
Nullable ndarray columns: Multidimensional columns fully support the nullable semantics (null_count, sentinel handling, null_policy) already available for scalar columns.
`from_pandas()` improvements: CTable.from_pandas() now creates the correct specialized backing storage for DictionarySpec, ListSpec, VLStringSpec, VLBytesSpec, and other variable-length scalar specifications.
Improved schema coverage: New CTable timestamp schema type and extended Column.info output with shape, chunks, and blocks descriptors.
Arg reductions: Added argmin() and argmax() for scalar and ndarray CTable columns, plus row-transformer support for generated columns such as per-row peak-hour or dominant-embedding-dimension features.
### CTable: Group-by and filtered aggregation
`CTable.group_by()`: The primary group-by interface. Call t.group_by("city", sort=True).agg({"qty": "mean"}) to produce a new CTable with aggregated results. Single-key and multi-key groupings are supported, along with convenience methods such as .size(), .count(), .sum(), .mean(), .min() and .max():
by_city = t.group_by("city", sort=True)
by_city.size() # COUNT(*)
by_city.sum("sales") # SUM(sales) per city
by_city.agg({"sales": ["sum", "mean"]}) # SUM(sales), AVG(sales) per city
Performance accelerators: Dedicated Cython fast paths deliver significant speedups: ~25× for float32/64 group-by keys, ~8× for integer and dictionary-code keys, and a general-purpose hash table for arbitrary float keys.
Filtered aggregate pushdown: The where= parameter is now accepted in aggregation methods, pushing the filter into the compute engine so that only matching rows are read and reduced.
Persistent grouped output: Group-by results can be saved directly to persistent storage via the urlpath= parameter.
`blosc2.group_reduce()`: New public function that performs group-by reduction over NDArray instances and CTable columns, with Cython-accelerated backends for common key/reduction combinations.
### CTable: Dictionary / categorical columns
`DictionarySpec` column type: Introduced a new dictionary-encoded (categorical) column type that stores string or integer codes mapped to a shared dictionary, providing compact storage and accelerated equality and membership queries.
Dictionary types in `where` clauses: Dictionary columns can be queried with the same where= expression syntax as other column types, including nested dotted-name access.
Improved display: CTable printing now adapts to the terminal width, and dictionary values are shown in their decoded form. Column.info has been extended with type details, shape, chunks, and blocks.
### CTable: Nested columns and field-name escaping
Dotted nested column access: Columns whose names contain literal . (e.g., "root.nested") are now fully addressable via the dotted accessor syntax in where expressions, __getitem__, and the public API.
Hierarchical `_cols` storage paths: The internal column storage layout now preserves a hierarchical structure that mirrors the logical nesting, improving introspection and interop.
Nested-field pipeline: A new flattened-storage pipeline with logical mapping preserves nested schema structure (field names, types, and hierarchy) through Arrow and Parquet import/export. For unnamed top-level list<struct<...>> Parquet files, the logical schema round-trips faithfully, though the original physical row grouping is intentionally not preserved.
Field-name escaping: Special characters (. and /) in column names are automatically escaped during schema construction and metadata round-trips.
### Parquet import/export improvements
Arrow serializer by default: CTable.from_parquet() now defaults to the Arrow serializer, providing better schema fidelity and nested-type support.
Progress reporting: A --progress flag and an ETA estimator have been added to the parquet-to-blosc2 CLI for long-running imports.
`--max-rows` parameter: CTable.from_parquet() and the CLI now accept max_rows to limit the number of imported rows.
`--timestamp-unit`: New CLI option to control timestamp unit conversion on import.
`--float-trunc-prec`: New CLI option to truncate floating-point precision on import.
Separated nested columns enabled by default: The separate_nested_cols flag is now True by default for both the Python API and the CLI, ensuring nested Arrow structs are always expanded into flat columns.
`list_serializer` parameter: New option to control how list-type columns are serialized, with sensible defaults for different list layouts.
Validation optimizations: Arrow datetime values are validated only during import, reducing runtime overhead on subsequent operations.
### TreeStore: Inline CTable support
CTables inside TreeStore: CTable objects can now be stored inline as items inside a TreeStore, enabling hierarchical storage that mixes arrays and tables in a single persistent container.
Cache hardening: TreeStore cache assignments now use defensive copies and cache effective object roots to avoid aliasing and stale-cache errors.
Examples and tutorials: New tutorials and docstring examples demonstrate how to store, retrieve, and query CTables within a TreeStore.
### Performance and usability enhancements
Faster open and import: blosc2.open() and store constructors now assume valid file extensions and defer column metainfo loading, making CTable.open() and package import noticeably faster.
`CTable.nrows` is now lazy: The row count is computed on demand rather than eagerly, speeding up open and schema-inspection workflows.
Accelerated scalar and small-slice access: The batch/list path for reading scalar values or small column slices has been overhauled, eliminating internal placeholder materialization and yielding lower latency.
Late-import optimizations: Heavy optional dependencies are imported lazily at the blosc2 package level, reducing the baseline import blosc2 overhead.
`iter_arrow_batches()` optimization: Avoids full Python object materialization of batches during iteration, reducing memory pressure.
`NDArray`-to-list conversion: Small optimization when converting NDArray objects to Python lists.
`_last_pos` invalidation skipped: Mid-table deletes no longer eagerly invalidate cached positional state, improving delete latency.
### Documentation, examples and benchmarks
API reference expanded: blosc2.group_reduce() has been added to the Sphinx reference, along with updated CTable, Column, and TreeStore pages.
New tutorials and examples: Added sections on CTable–TreeStore integration, nested fields, dictionary columns, aggregates, grouping and querying with where=.
New benchmarks: Graph benchmarks for CTable insert time, column count, memory usage, and where= queries, plus dedicated group-by, nested-filter, and Parquet round-trip benchmarks.
### Fixes and compatibility
Null and NaN handling: NumPy scalar null sentinels are now normalized to plain Python scalars, and floating-point NaN sentinels are treated consistently with Python float('nan').
Empty aggregate results: Filtered aggregations that produce no rows now handle the empty result gracefully.
Generated column safety: Accessing a stalled (unfillable) generated column now raises a clear exception instead of producing undefined results.
Miniexpr bundling: Miniexpr’s bundled libtcc and related runtime files are now kept inside the blosc2 package, avoiding conflicts with other TCC installations.
Test improvements: Torch-dependent tests are marked as heavy, PyArrow-optional tests are skipped when the library is absent, and parametrization matrices have been trimmed to reduce CI time.
Missing Cython validation: Added validation guards for several Cython extension functions that previously lacked explicit error checking.
C-Blosc2 update: Bundled C-Blosc2 has been updated to the latest version (3.0.3).
``blosc2.open()`` default mode changed from 'a' to 'r': Removed the FutureWarning that was added to prepare for this transition.
Updated c-blosc2 for memory leak and other bug fixes
Updated c-blosc2 for memory leak and other bug fixes
blosc2.CTable, a new columnar table container for compressed, typed columns. CTables support dataclass- and schema-based construction, row iteration, column access, table views, head() / tail() / sample(), sorting, selection and compact where expressions.TreeStore, with support for blosc2.open(), CTable.open(), CTable.load(), CTable.save(), CTable.to_b2d() and CTable.to_b2z(). CTable views can be saved too, and .b2z/.b2d path handling has been tightened.append(), extend(), delete(), compact(), add_column(), drop_column(), rename_column() and related schema validation.LazyExpr/NDArray masks in CTable.__getitem__, iter_sorted() and indexing support for .b2z tables.ListArray / ObjectArray, including vlstring and vlbytes schema specs, fixed-length string/bytes import support and list/struct Arrow/Parquet round-trips.CTable.from_arrow_batches() improvements and a new parquet-to-blosc2 CLI utility.blosc2.Index as the unified public index handle, plus APIs such as create_index(), compact_index(), iter_sorted(), will_use_index() and related query explanation support.blosc2.argsort() and refactored indexing APIs around explicit index enums and sorting helpers.tmpdir support for full out-of-core indexes.C2Array, LazyExpr and DSL LazyUDF objects.blosc2.Ref for serializing external references, plus examples for b2object bundles and persisted expressions/UDFs.blosc2.load() as a convenience loader.vlmeta support to LazyArray objects.DictStore, allowing reopened proxies to refill caches after read-only opens, relaxing DictStore/TreeStore suffix requirements and adding DictStore.to_b2d().blosc2.open() by trying standard opens first and warning on implicit append mode.ObjectArray for fully general object data and renamed the earlier VLArray work accordingly; added ListArray docstrings and Arrow integration improvements.blosc2.struct() and blosc2.object() for nested/fully general column declarations.fromiter() with direct chunked construction and substantially lower peak memory use.asarray() behavior for NDArray inputs when copy-inducing keyword arguments are supplied.SChunk.reorder_offsets().BatchArray defaults and documentation; the default compression level is now tuned for faster lookup/scan behavior.parquet-to-blosc2 command with options such as --max-rows, --parquet-batch-size, --blosc2-items-per-block and --use-dict./tmp..b2z double-open corruption caused by GC-triggered repacking and made temporary .b2z unpacking default to the source file directory.arange() regressions and several pre-existing set_slice error-handling issues.Update miniexpr version to fix bug on Ubuntu-arm64.
Update miniexpr version to fix bug on Ubuntu-arm64.
Add DSL kernel functionality for faster, compiled, user-defined functions which broadly respect python syntax and implement the LazyArray interface. S
LazyArray interface. See the introductory tutorial at: https://blosc.org/python-blosc2/getting_started/tutorials/03.lazyarray-udf-kernels.htmlDictStore, TreeStore, and EmbedStore now accept mmap_mode="r"
when opened with mode="r" (including via blosc2.open for .b2d,
.b2z, and .b2e).blosc2.open() time. Fixes #546.cumulative_sum and cumulative_prod functions for Array API compliance.endswith and startswith and extend contains to support strings and offer miniexpr multithreaded computation when possible.arange/linspace constructors by 6-10x.filters and filters_meta.resize and constructors so that chunks may be set independently of shape, and arrays may be extended from empty consistently.miniexpr integration, interface, and support.The main change is hyperfast fully multithreaded computation with miniexpr (final PR * Miniexpr for Windows by @FrancescAlted in https://github.com/Bl
The main change is hyperfast fully multithreaded computation with miniexpr (final PR * Miniexpr for Windows by @FrancescAlted in https://github.com/Blosc/python-blosc2/pull/565). In addition, the internal wheel structure has been changed to implement PEP 427 (@lshaw8317 in https://github.com/Blosc/python-blosc2/pull/560). In addition:
Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.12.2...v4.0.0
LazyArray interface. See the introductory tutorial at: https://blosc.org/python-blosc2/getting_started/tutorials/03.lazyarray-udf-kernels.htmlDictStore, TreeStore, and EmbedStore now accept mmap_mode="r"
when opened with mode="r" (including via blosc2.open for .b2d,
.b2z, and .b2e).blosc2.open() time. Fixes #546.cumulative_sum and cumulative_prod functions for Array API compliance.endswith and startswith and extend contains to support strings and offer miniexpr multithreaded computation when possible.arange/linspace constructors by 6-10x.filters and filters_meta.resize and constructors so that chunks may be set independently of shape, and arrays may be extended from empty consistently.miniexpr integration, interface, and support.This is a beta version with hyperfast multithreaded expression calculatio via the incorporation of miniexpr; as well as better support for plugins (st
This is a beta version with hyperfast multithreaded expression calculatio via the incorporation of miniexpr; as well as better support for plugins (stay tuned for blosc2_openzl plugin!),
Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.12.2...v4.0.0-b1
BLOSC2_ENABLE_MINIEXPR_WINDOWS=1 to override this for testing.Hotfix to change WASM wheel hosting to separate repo
Allow saving of numba-decorated lazyudfs by @lshaw8317 in https://github.com/Blosc/python-blosc2/pull/538
LazyUDF objects can now be saved to disk
LazyUDF objects can now be saved to disk__matmul__ NumPy ufunc now passed to blosc2.matmulLazyUDF.compute is now much more robust and functionalget_chunk method for LazyExpr is more efficient and enabled for general LazyArray objectsLazyExpr calculation can now be done even with expressions with pure scalar operands, e.g 10 * 3 +1..Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.11.1...v3.12.0
import in (saved) LazyUDF objects✅ Change the NDArray.size to return the number of elements in array, instead of the size of the array in bytes ✅ Bug fixes for lazy expressions to all
✅ Change the NDArray.size to return the number of elements in array, instead of the size of the array in bytes ✅ Bug fixes for lazy expressions to allow a wider range of functionality ✅ Small bug fix for slice indexing with step larger than chunksize ✅ Tweak automatic chunk sizing of results for certain (e.g. linalg) operations to enhance performance ✅ Various cosmetic fixes and streamlining (thanks to the indefatigable @DimitriPapadopoulos)
Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.11.0...v3.11.1
LazyUDF objects can now be saved to disk__matmul__ NumPy ufunc now passed to blosc2.matmulLazyUDF.compute is now much more robust and functionalget_chunk method for LazyExpr is more efficient and enabled for general LazyArray objectsLazyExpr calculation can now be done even with expressions with pure scalar operands, e.g 10 * 3 +1..Small optimisation for chunking in lazy expressions
cat2cloud (PR #511)squeeze to return view (rather than modify array in-place) (PR #518)setitem to load general array inputs into NDArrays (PR #517)Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.10.2...v3.11.0
NDArray.size to return the number of elements in array,
instead of the size of the array in bytes. This follows the array
API, so it is considered a fix, and takes precedence over a possible
backward incompatibility.LazyExpr.compute() now honors the out parameter for regular expressions (and not only for reductions). See PR #506.
out parameter for regular expressions (and not only for reductions). See PR #506.cat2cloud (PR #511)squeeze to return view (rather than modify array in-place) (PR #518)setitem to load general array inputs into NDArrays (PR #517)Python 3.14 by @DimitriPapadopoulos in https://github.com/Blosc/python-blosc2/pull/504
out parameter for regular expressions (and not only for reductions). See PR #506.Update documentation for thread management by @orena1 in https://github.com/Blosc/python-blosc2/pull/495
Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.9.1...v3.10.0
tanh and tanhBumped to numexpr 2.13.1 to incorporate new maximum/minimum NaN handling and +/* for booleans which matches NumPy behaviour.
Operand to ensure access to basic methods like __add__ for all computable objects (NDArray, LazyExpr, LazyArray etc.) (PR ##500).Most changes come from PR #467 relating to array-api compliance.
Most changes come from PR #467 relating to array-api compliance.
C-Blosc2 internal library updated to latest 2.21.3, increasing MAX_DIMS from 8 to 16
numexpr version requirement pushed to 2.13.0 to incorporate round, sign, signbit, copysign, nextafter, hypot, maximum, minimum, trunc, log2 functions, as well as allow integer outputs for certain functions when integr arguments are passed. We also add floor division (//) and full dual bitwise (logical) AND, OR, XOR, NOT
support for integer (bool) arrays.
Extended linear algebra functionality, offering generalised matrix multiplication for arrays of arbitrary dimension via tensordot and an improved matmul. In addition, introduced vecdot, diagonal and outer, as well as useful indexing and associated functions such as take, take_along_axis, meshgrid and broadcast_to.
Added many ufuncs and methods (around 60) to NDArray to bring the library into further alignment with the array-api. Introduced a chunkwise lazyudf paradigm which is very powerful in order to implement clip and logaddexp.
Fixed a subtle but important bug for expand_dims (PR #479, PR #483) relating to reference counting for views.
Various typos and other fixes due to @DimitriPapadopoulos !
Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/Blosc/python-blosc2/pull/458
as_ffi_ptr to NDArray by @barakugav in https://github.com/Blosc/python-blosc2/pull/460Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.7.2...v3.8.0
Most changes come from PR #467 relating to array-api compliance.
C-Blosc2 internal library updated to latest 2.21.3, increasing MAX_DIMS from 8 to 16
numexpr version requirement pushed to 2.13.0 to incorporate
round, sign, signbit, copysign, nextafter, hypot,
maximum, minimum, trunc, log2 functions, as well as allow
integer outputs for certain functions when integr arguments are passed.
We also add floor division (//) and full dual bitwise (logical) AND, OR, XOR, NOT
support for integer (bool) arrays.
Extended linear algebra functionality, offering generalised matrix multiplication
for arrays of arbitrary dimension via tensordot and an improved matmul. In addition,
introduced vecdot, diagonal and outer, as well as useful indexing and associated functions such as take, take_along_axis, meshgrid and broadcast_to.
Added many ufuncs and methods (around 60) to NDArray to bring the library into further alignment with the array-api. Introduced a chunkwise lazyudf paradigm which is very powerful in order to implement clip and logaddexp.
Fixed a subtle but important bug for expand_dims (PR #479, PR #483) relating to reference counting for views.
C-Blosc2 internal library updated to latest 2.21.1.
C-Blosc2 internal library updated to latest 2.21.1.
Revert signature of TreeStore.__init__ for making benchmarks to get back
to normal performance.
C-Blosc2 internal library updated to latest 2.21.2.
numexpr version requirement pushed to 2.12.1 to incorporate
isnan, isfinite, isinf functions.
Indexing is now supported extensively and reasonably optimally for slices with negative steps and general boolean arrays, with both get/setitem having equal functionality. In PR #459 we extended the 1D fast path to general N-D, with consequent speedups. In PR # we allowed fancy indexing and general slicing with negative steps for set and getitem, with a memory-optimised path for setitem.
Various attributes and methods for the NDArray class, as well as functions, have
been added to increase compliance with the array-api standard. In addition,
linspace and arange functions have been made more numerically stable and now strictly
comply even with difficult floating-point edge cases.
Added C2Array.slice() method and C2Array.nbytes, C2Array.cbytes, C2Array.cratio, C2Array.vlmeta and C2Array.info properties (PR #455).
Added C2Array.slice() method and C2Array.nbytes, C2Array.cbytes, C2Array.cratio, C2Array.vlmeta and C2Array.info properties (PR #455).
Many usability improvements to the TreeStore class and friends.
New section about TreeStore in basics NDArray tutorial.
New blog post about TreeStore usage and performance at: https://www.blosc.org/posts/new-treestore-blosc2
C-Blosc2 internal library updated to latest 2.21.0.
C-Blosc2 internal library updated to latest 2.21.1.
Revert signature of TreeStore.__init__ for making benchmarks to get back
to normal performance.
Overhaul of documentation (API reference and Tutorials)
Overhaul of documentation (API reference and Tutorials)
Improvements to lazy expression indexing and in particular much more efficient memory usage when applying non-unit steps (PR #446).
Extended functionality of expand_dims to match that of NumPy (note that this breaks the previous API) (PR #453).
The biggest change is in the form of three new data storage classes (EmbedStore, DictStore and TreeStore) which allow for the efficient storage of heterogeneous array data (PR #451). EmbedStore is essentially an SChunk wrapper which can be stored on-disk or in-memory; DictStore allows for mixed storage across memory, disk or indeed remote; and TreeStore is a hieracrhically-formatted version of DictStore which mimics the HDF5 file format. Write, access and storage performance are all very competitive with other packages - see plots here.
Added C2Array.slice() method and C2Array.nbytes, C2Array.cbytes, C2Array.cratio, C2Array.vlmeta and C2Array.info properties (PR #455).
Many usability improvements to the TreeStore class and friends.
New section about TreeStore in basics NDArray tutorial.
New blog post about TreeStore usage and performance at: https://www.blosc.org/posts/new-treestore-blosc2
C-Blosc2 internal library updated to latest 2.21.0.
Expose the oindex C-level functionality in Blosc2 for NDArray.
Changes in Blosc2 3.6.1
Changes from Blosc2 3.6.0
Expose the oindex C-level functionality in Blosc2 for NDArray.
Implement fancy indexing which closely matches NumPy functionality, using ndindex library. Includes a fast path for 1D arrays, based on Zarr's implementation.
A major refactoring of slicing for lazy expressions using ndindex. We have also added support for slices with non-unit steps for reduction expressions, which has introduced improvements that could be incorporated into other lazy expression machinery in the future.
More complex slicing is now supported.
Minor bug fixes to ensure that Blosc2 indexing does not introduce dummy dimensions when NumPy does not, and a more comprehensive squeeze function which squeezes specified dimensions.
Overhaul of documentation (API reference and Tutorials)
Improvements to lazy expression indexing and in particular much more efficient memory usage when applying non-unit steps (PR #446).
Extended functionality of expand_dims to match that of NumPy (note that this breaks the previous API) (PR #453).
The biggest change is in the form of three new data storage classes (EmbedStore, DictStore and TreeStore) which allow for the efficient storage of heterogeneous array data (PR #451). EmbedStore is essentially an SChunk wrapper which can be stored on-disk or in-memory; DictStore allows for mixed storage across memory, disk or indeed remote; and TreeStore is a hieracrhically-formatted version of DictStore which mimics the HDF5 file format. Write, access and storage performance are all very competitive with other packages - see plots here.
Expose the oindex C-level functionality in Blosc2 for NDArray.
Changes in Blosc2 3.6.0
Expose the oindex C-level functionality in Blosc2 for NDArray.
Implement fancy indexing which closely matches NumPy functionality, using
ndindex library. Includes a fast path for 1D arrays, based on Zarr's implementation.
A major refactoring of slicing for lazy expressions using ndindex. We have also
added support for slices with non-unit steps for reduction expressions, which has introduced
improvements that could be incorporated into other lazy expression machinery in the future.
More complex slicing is now supported.
Minor bug fixes to ensure that Blosc2 indexing does not introduce dummy dimensions when NumPy does not,
and a more comprehensive squeeze function which squeezes specified dimensions.
Reduced memory usage when computing slices of lazy expressions. This is a significant improvement for large arrays (up to 20x less). Also, we have add
Reduced memory usage when computing slices of lazy expressions. This is a significant improvement for large arrays (up to 20x less). Also, we have added a fast path for slices that are small and fit in memory, which can be up to 20x faster than the previous implementation. See PR #430.
blosc2.concatenate() has been renamed to blosc2.concat().
This is in line with the Array API.
The old name is still available for backward compatibility, but it will
be removed in a future release.
Improve mode handling for concatenating to disk. See PR #428. Useful for concatenating arrays that are stored in disk, and allows specifying the mode to use when concatenating.
Expose the oindex C-level functionality in Blosc2 for NDArray.
Implement fancy indexing which closely matches NumPy functionality, using
ndindex library. Includes a fast path for 1D arrays, based on Zarr's implementation.
A major refactoring of slicing for lazy expressions using ndindex. We have also
added support for slices with non-unit steps for reduction expressions, which has introduced
improvements that could be incorporated into other lazy expression machinery in the future.
More complex slicing is now supported.
Minor bug fixes to ensure that Blosc2 indexing does not introduce dummy dimensions when NumPy does not,
and a more comprehensive squeeze function which squeezes specified dimensions.
New blosc2.stack() function for stacking multiple arrays along a new axis. Useful for creating multi-dimensional arrays from multiple 1D arrays. See P
New blosc2.stack() function for stacking multiple arrays along a new axis.
Useful for creating multi-dimensional arrays from multiple 1D arrays.
See PR #427. Thanks to Luke Shaw for the implementation!
Blog: https://www.blosc.org/posts/blosc2-new-concatenate/#stacking-arrays
New blosc2.expand_dims() function for expanding the dimensions of an array.
This is useful for adding a new axis to an array, similar to NumPy's np.expand_dims().
See PR #427. Thanks to Luke Shaw for the implementation!
Reduced memory usage when computing slices of lazy expressions. This is a significant improvement for large arrays (up to 20x less). Also, we have added a fast path for slices that are small and fit in memory, which can be up to 20x faster than the previous implementation. See PR #430.
blosc2.concatenate() has been renamed to blosc2.concat().
This is in line with the Array API.
The old name is still available for backward compatibility, but it will
be removed in a future release.
Improve mode handling for concatenating to disk. See PR #428. Useful for concatenating arrays that are stored in disk, and allows specifying the mode to use when concatenating.
This release adds significant new functionality in the form of concatenate. We support general concatenation of ndarrays, and offer an optimised path
This release adds significant new functionality in the form of concatenate. We support general concatenation of ndarrays, and offer an optimised path with significant speedups for the case of concatenating arrays with compatible chunk and blockshapes. In addition, there are bug fixes and more functionality for slicing of lazyexprs, and the possibility to jit compile user-defined functions which operate on pandas objects using the blosc2 engine.
Full Changelog: https://github.com/Blosc/python-blosc2/compare/v3.3.4...v3.4.0
New blosc2.stack() function for stacking multiple arrays along a new axis.
Useful for creating multi-dimensional arrays from multiple 1D arrays.
See PR #427. Thanks to Luke Shaw for the implementation!
Blog: https://www.blosc.org/posts/blosc2-new-concatenate/#stacking-arrays
New blosc2.expand_dims() function for expanding the dimensions of an array.
This is useful for adding a new axis to an array, similar to NumPy's np.expand_dims().
See PR #427. Thanks to Luke Shaw for the implementation!
This is a bugfix release, with some minor optimizations. We further improved the correct chaining of *string* lazy expressions (to allow operands with
This is a bugfix release, with some minor optimizations. We further improved the correct chaining of string lazy expressions (to allow operands with more diverse data types). In addition, both indexing and where expressions are now supported within string lazy expressions. Finally, casting rules have been improved to be more consistent with NumPy. In summary:
Expand possibilities for chaining string-based lazy expressions to incorporate data types which do not have shape attribute, e.g. int, float etc. See #406 and PR #411.
Enable slicing within string-based lazy expressions. See PR #414.
Improved casting for string-based lazy expressions.
Documentation improvements, see PR #410.
Compatibility fixes for working with h5py files.
Added C-level concatenate function in response to community request. When possible, uses an optimised path which avoids decompression and recompression, giving a significant performance boost. See PR #423.
Slicing has been added to string-based lazyexprs, so that one may use
expressions like expr[1:3] +1 to compute a slice of the expression. This is useful
for getting a sub-expression of a larger expression, and it works with both
string-based and lazy expressions. See PR #417.
Relatedly, the behaviour of the slice parameter in the compute() method of LazyExpr has been made more consistent and is now better documented, so that results are as expected. See PR #419.
UDF support for pandas has been added to allow for the use of blosc2.jit. See PR #418. Thanks to @datapythonista for the implementation!
Expand possibilities for chaining string-based lazy expressions to include main operand types (LazyExpr and NDArray). Still have to incorporate other
Expand possibilities for chaining string-based lazy expressions to include main operand types (LazyExpr and NDArray). Still have to incorporate other data types (which do not have shape attribute, e.g. int, float etc.). See #406.
Fix indexing for lazy expressions, and allow use of None in getitem. See PR #402.
Fix incorrect appending of dim to computed reductions. See PR #404.
Fix blosc2.linspace() for incompatible num/shape. See PR #408.
Add support for NumPy dtypes that are n-dimensional (e.g.
np.dtype(("<i4,>f4", (10,))),).
New MAX_DIM constant for the maximum number of dimensions supported. This is useful for checking if a given array is too large to be handled.
More refinements on guessing cache sizes for Linux.
Update to C-Blosc2 2.17.2.dev. Now, we are forcing the flush of modified pages only in write mode for mmap files. This fixes mmap issues on Windows. Thanks to @JanSellner for the implementation.
Expand possibilities for chaining string-based lazy expressions to incorporate data types which do not have shape attribute, e.g. int, float etc. See #406 and PR #411.
Enable slicing within string-based lazy expressions. See PR #414.
Improved casting for string-based lazy expressions.
Documentation improvements, see PR #410.
Compatibility fixes for working with h5py files.
Fixed a bug in the determination of chunk shape for the NDArray constructor. This was causing problems when creating NDArray instances with a CPU that
Fixed a bug in the determination of chunk shape for the NDArray constructor.
This was causing problems when creating NDArray instances with a CPU that
was reporting a L3 cache size close (or exceeding) 2 GB. See PR #392.
Fixed a bug preventing the correct chaining of string lazy expressions for
logical operators (&, |, ^...). See PR #391.
More performance optimization for blosc2.permute_dims. Thanks to
Ricardo Sales Piquer (@ricardosp4) for the implementation.
Now, storage defaults (blosc2.storage_dflts) are honored, even if no
storage= param is used in constructors.
We are distributing Python 3.10 wheels now.
Expand possibilities for chaining string-based lazy expressions to include main operand types (LazyExpr and NDArray). Still have to incorporate other data types (which do not have shape attribute, e.g. int, float etc.). See #406.
Fix indexing for lazy expressions, and allow use of None in getitem. See PR #402.
Fix incorrect appending of dim to computed reductions. See PR #404.
Fix blosc2.linspace() for incompatible num/shape. See PR #408.
Add support for NumPy dtypes that are n-dimensional (e.g.
np.dtype(("<i4,>f4", (10,))),).
New MAX_DIM constant for the maximum number of dimensions supported. This is useful for checking if a given array is too large to be handled.
More refinements on guessing cache sizes for Linux.
Update to C-Blosc2 2.17.2.dev. Now, we are forcing the flush of modified pages only in write mode for mmap files. This fixes mmap issues on Windows. Thanks to @JanSellner for the implementation.
This replaces to previous transpose() function, which is now deprecated. See PR #384. Thanks to Ricardo Sales Piquer (@ricardosp4).
In our effort to better adapt to better adapt to the array API (https://data-apis.org/array-api/latest/), we have introduced permute_dims() and matrix_transpose() functions, and the .T property. This replaces to previous transpose() function, which is now deprecated. See PR #384. Thanks to Ricardo Sales Piquer (@ricardosp4).
Constructors like arange(), linspace() and fromiter() now
use far less memory when creating large arrays. As an example, a 5 TB
array of 8-byte floats now uses less than 200 MB of memory instead of
170 GB previously. See PR #387.
Now, when opening a lazy expression with blosc2.open(), and there is
a missing operand, the open still works, but the dtype and shape
attributes are None. This is useful for lazy expressions that have
lost some operands, but you still want to open them for inspection.
See PR #385.
Added an example of getting a slice out of a C2Array.
Fixed a bug in the determination of chunk shape for the NDArray constructor.
This was causing problems when creating NDArray instances with a CPU that
was reporting a L3 cache size close (or exceeding) 2 GB. See PR #392.
Fixed a bug preventing the correct chaining of string lazy expressions for
logical operators (&, |, ^...). See PR #391.
More performance optimization for blosc2.permute_dims. Thanks to
Ricardo Sales Piquer (@ricardosp4) for the implementation.
Now, storage defaults (blosc2.storage_dflts) are honored, even if no
storage= param is used in constructors.
We are distributing Python 3.10 wheels now.
New blosc2.transpose() function for transposing 2D NDArray instances natively. See PR #375 and docs at https://www.blosc.org/python-blosc2/reference/a
New blosc2.transpose() function for transposing 2D NDArray instances
natively. See PR #375 and docs at
https://www.blosc.org/python-blosc2/reference/autofiles/operations_with_arrays/blosc2.transpose.html#blosc2.transpose
See also our new blog about this: https://www.blosc.org/posts/transpose-compressed-matrices/
Thanks to Ricardo Sales Piquer (@ricardosp4) for the implementation.
New fast path for NDArray.slice() for getting slices that are aligned with
underlying chunks. This is a common operation when working with NDArray
instances, and now it is up to 40x faster in our benchmarks (see PR #380).
Returned NDArray object in NDarray.slice() now defaults to original
codec/clevel/filters. The previous behavior was to use the default
codec/clevel/filters. See PR #378. Thanks to Luke Shaw (@lshaw8317).
Several English edits in the documentation. Thanks to Luke Shaw (@lshaw8317) for his help in this area.
In our effort to better adapt to better adapt to the array API (https://data-apis.org/array-api/latest/), we have introduced permute_dims() and matrix_transpose() functions, and the .T property. This replaces to previous transpose() function, which is now deprecated. See PR #384. Thanks to Ricardo Sales Piquer (@ricardosp4).
Constructors like arange(), linspace() and fromiter() now
use far less memory when creating large arrays. As an example, a 5 TB
array of 8-byte floats now uses less than 200 MB of memory instead of
170 GB previously. See PR #387.
Now, when opening a lazy expression with blosc2.open(), and there is
a missing operand, the open still works, but the dtype and shape
attributes are None. This is useful for lazy expressions that have
lost some operands, but you still want to open them for inspection.
See PR #385.
Added an example of getting a slice out of a C2Array.
The array containers are now using the __array_interface__ protocol to expose the data in the array. This allows for better interoperability with othe
The array containers are now using the __array_interface__ protocol to
expose the data in the array. This allows for better interoperability with
other libraries that support the __array_interface__ protocol, like NumPy,
CuPy, etc. Now, the range of functions that can be used within the blosc2.jit
decorator is way larger, and essentially all NumPy functions should work now.
See examples at: https://github.com/Blosc/python-blosc2/blob/main/examples/ndarray/jit-numpy-funcs.py See benchmarks at: https://github.com/Blosc/python-blosc2/blob/main/bench/ndarray/jit-numpy-funcs.py
The performance of constructors like arange(), linspace() and fromiter()
has been improved. Now, they can be up to 3x faster, specially with large
arrays.
C-Blosc2 updated to 2.17.1. This fixes various UB as well as compiler warnings.
New blosc2.transpose() function for transposing 2D NDArray instances
natively. See PR #375 and docs at
https://www.blosc.org/python-blosc2/reference/autofiles/operations_with_arrays/blosc2.transpose.html#blosc2.transpose
Thanks to Ricardo Sales Piquer (@ricardosp4) for the implementation.
New fast path for NDArray.slice() for getting slices that are aligned with
underlying chunks. This is a common operation when working with NDArray
instances, and now it is up to 40x faster in our benchmarks (see PR #380).
Returned NDArray object in NDarray.slice() now defaults to original
codec/clevel/filters. The previous behavior was to use the default
codec/clevel/filters. See PR #378. Thanks to Luke Shaw (@lshaw8317).
Several English edits in the documentation. Thanks to Luke Shaw (@lshaw8317) for his help in this area.
Structured arrays can be larger than 255 bytes now. This was a limitation in the previous versions, but now it is gone (the new limit is ~512 MB, whic
Structured arrays can be larger than 255 bytes now. This was a limitation in the previous versions, but now it is gone (the new limit is ~512 MB, which I hope will be enough for some time).
New blosc2.matmul() function for computing matrix multiplication on NDArray instances. This allows for efficient computations on compressed data that can be in-memory, on-disk and in the network. See here for more information.
Support for building WASM32 wheels. This is a new feature that allows to build wheels for WebAssembly 32-bit platforms. This is useful for running Python code in the browser.
Tested support for NumPy<2 (at least 1.26 series). Now, the library should work with NumPy 1.26 and up.
C-Blosc2 updated to 2.17.0.
httpx has been replaced by the requests library for the remote proxy. This was necessary to avoid the need of the httpx library, which is not supported by Pyodide.
The array containers are now using the __array_interface__ protocol to
expose the data in the array. This allows for better interoperability with
other libraries that support the __array_interface__ protocol, like NumPy,
CuPy, etc. Now, the range of functions that can be used within the blosc2.jit
decorator is way larger, and essentially all NumPy functions should work now.
See examples at: https://github.com/Blosc/python-blosc2/blob/main/examples/ndarray/jit-numpy-funcs.py See benchmarks at: https://github.com/Blosc/python-blosc2/blob/main/bench/ndarray/jit-numpy-funcs.py
The performance of constructors like arange(), linspace() and fromiter()
has been improved. Now, they can be up to 3x faster, specially with large
arrays.
C-Blosc2 updated to 2.17.1. This fixes various UB as well as compiler warnings.
Quick release to fix an issue with version number in the package (was reporting 3.0.0 instead of 3.1.0).
Structured arrays can be larger than 255 bytes now. This was a limitation in the previous versions, but now it is gone (the new limit is ~512 MB, which I hope will be enough for some time).
New blosc2.matmul() function for computing matrix multiplication on NDArray
instances. This allows for efficient computations on compressed data that
can be in-memory, on-disk and in the network. See
here
for more information.
Support for building WASM32 wheels. This is a new feature that allows to build wheels for WebAssembly 32-bit platforms. This is useful for running Python code in the browser.
Tested support for NumPy<2 (at least 1.26 series). Now, the library should work with NumPy 1.26 and up.
C-Blosc2 updated to 2.17.0.
httpx has replaced by requests library for the remote proxy. This has been
done to avoid the need of the httpx library, which is not supported by
Pyodide.
Optimizations for the compute engine. Now, it is faster and uses less memory. In particular, careful attention has been paid to the memory handling, a
Optimizations for the compute engine. Now, it is faster and uses less memory. In particular, careful attention has been paid to the memory handling, as this is the main bottleneck for the compute engine in many instances.
Improved detection of CPU cache sizes for Linux and macOS. In particular, support for multi-CCX (AMD EPYC) and multi-socket systems has been implemented. Now, the library should be able to detect the cache sizes for most of the CPUs out there (specially on Linux).
Optimization on NDArray slicing when the slice is a single chunk. This is a common operation when working with NDArray instances, and now it is faster.
New blosc2.evaluate() function for evaluating expressions on NDArray/NumPy
instances. This a drop-in replacement of numexpr.evaluate(), but with the
next improvements:
See here for more information.
New blosc2.jit decorator for allowing NumPy expressions to be computed
using the Blosc2 compute engine. This is a powerful feature that allows for
efficient computations on compressed data, and supports advanced features like
reductions, filters and broadcasting. See here for more information.
Support out= in blosc2.mean(), blosc2.std() and blosc2.var() reductions
(besides blosc2.sum() and blosc2.prod()).
Bumped to use latest C-Blosc2 sources (2.16.0).
The cache for cpuinfo is now stored in ${HOME}/.cache/python-blosc2/cpuinfo.json
instead of ${HOME}/.blosc2-cpuinfo.json; you can get rid of the latter, as
the former is more standard (see PR #360). Thanks to Jonas Lundholm Bertelsen
(@jonaslb).
A persistent cache for cpuinfo (stored in $HOME/.blosc2-cpuinfo.json) is now used to avoid repeated calls to the cpuinfo library. This accelerates the
A persistent cache for cpuinfo (stored in $HOME/.blosc2-cpuinfo.json) is
now used to avoid repeated calls to the cpuinfo library. This accelerates
the startup time of the library considerably (up to 5x on my box).
We should be creating conda packages now. Thanks to @hmaarrfk for his assistance in this area.
Optimizations for the compute engine. Now, it is faster and uses less memory. In particular, careful attention has been paid to the memory handling, as this is the main bottleneck for the compute engine in many instances.
Improved detection of CPU cache sizes for Linux and macOS. In particular, support for multi-CCX (AMD EPYC) and multi-socket systems has been implemented. Now, the library should be able to detect the cache sizes for most of the CPUs out there (specially on Linux).
Optimization on NDArray slicing when the slice is a single chunk. This is a common operation when working with NDArray instances, and now it is faster.
New blosc2.evaluate() function for evaluating expressions on NDArray/NumPy
instances. This a drop-in replacement of numexpr.evaluate(), but with the
next improvements:
See here for more information.
New blosc2.jit decorator for allowing NumPy expressions to be computed
using the Blosc2 compute engine. This is a powerful feature that allows for
efficient computations on compressed data, and supports advanced features like
reductions, filters and broadcasting. See
here
for more information.
Support out= in blosc2.mean(), blosc2.std() and blosc2.var() reductions
(besides blosc2.sum() and blosc2.prod()).
Bumped to use latest C-Blosc2 sources (2.16.0).
The cache for cpuinfo is now stored in ${HOME}/.cache/python-blosc2/cpuinfo.json
instead of ${HOME}/.blosc2-cpuinfo.json; you can get rid of the latter, as
the former is more standard (see PR #360). Thanks to Jonas Lundholm Bertelsen
(@jonaslb).
Now you can get and set the whole values of VLMeta instances with the vlmeta[:] syntax. The get part is syntactic sugar for vlmeta.getall() actually.
Now you can get and set the whole values of VLMeta instances with the vlmeta[:] syntax.
The get part is syntactic sugar for vlmeta.getall() actually.
blosc2.copy() now honors cparams= parameter.
Now, compiling the package with USE_SYSTEM_BLOSC2 envar set to 1 will use the
system-wide Blosc2 library. This is useful for creating packages that do not want
to bundle the Blosc2 library (e.g. conda).
Several changes in the build process to enable conda-forge packaging.
Now, blosc2.pack_tensor() can pack empty tensors/arrays. Fixes #290.
A persistent cache for cpuinfo (stored in $HOME/.blosc2-cpuinfo.json) is
now used to avoid repeated calls to the cpuinfo library. This accelerates
the startup time of the library considerably (up to 5x on my box).
We should be creating conda packages now. Thanks to @hmaarrfk for his assistance in this area.
Your coding agent can read these notes before it upgrades. Set up the MCP server →