Skip to content

Commit 5696a03

Browse files
committed
New Column.array accessor to underlying storage container, without null-value processing
1 parent 833e46c commit 5696a03

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

src/blosc2/ctable.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,24 @@ def is_ndarray(self) -> bool:
818818
col = self._table._schema.columns_by_name.get(self._col_name)
819819
return col is not None and isinstance(col.spec, NDArraySpec)
820820

821+
@property
822+
def array(self):
823+
"""The underlying storage container for this column, without null-value processing.
824+
825+
Returns the raw :class:`blosc2.NDArray`, :class:`~blosc2.ListArray`,
826+
:class:`~blosc2.DictionaryColumn`, or scalar varlen array directly,
827+
allowing bulk reads that bypass the null-sentinel scan in
828+
:meth:`__getitem__`.
829+
830+
Raises :exc:`AttributeError` for computed (virtual) columns, which have
831+
no backing storage.
832+
"""
833+
if self.is_computed:
834+
raise AttributeError(
835+
f"Column {self._col_name!r} is a computed column and has no underlying array"
836+
)
837+
return self._raw_col
838+
821839
@property
822840
def _valid_rows(self):
823841
if self._mask is None:

tests/ctable/test_column.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,5 +1041,89 @@ class R:
10411041
np.testing.assert_allclose(t["val"][:], np.arange(n_live, dtype=np.float64) * 7.0)
10421042

10431043

1044+
# -------------------------------------------------------------------
1045+
# Column.array property
1046+
# -------------------------------------------------------------------
1047+
1048+
1049+
@dataclass
1050+
class ArrayRow:
1051+
value: float = blosc2.field(blosc2.float64())
1052+
label: str = blosc2.field(blosc2.dictionary())
1053+
name: str = blosc2.field(blosc2.string(max_length=32))
1054+
tags: list[str] = blosc2.field( # noqa: RUF009
1055+
blosc2.list(blosc2.string(max_length=16))
1056+
)
1057+
1058+
1059+
def test_column_array_ndarray():
1060+
"""array on a numeric column returns the underlying NDArray directly."""
1061+
t = CTable(Row, new_data=DATA20)
1062+
raw = t.id.array
1063+
assert isinstance(raw, blosc2.NDArray)
1064+
assert raw is t._cols["id"]
1065+
1066+
1067+
def test_column_array_dictionary():
1068+
"""array on a dictionary column returns the underlying DictionaryColumn."""
1069+
from blosc2.dictionary_column import DictionaryColumn
1070+
1071+
t = CTable(DictRow, new_data=[("acme", 1.0), ("globex", 2.0), ("acme", 3.0)])
1072+
raw = t.vendor.array
1073+
assert isinstance(raw, DictionaryColumn)
1074+
assert raw is t._cols["vendor"]
1075+
1076+
1077+
def test_column_array_varlen_scalar():
1078+
"""array on a varlen-string column returns the underlying _ScalarVarLenArray."""
1079+
from blosc2.scalar_array import _ScalarVarLenArray
1080+
1081+
@dataclass
1082+
class VLRow:
1083+
note: str = blosc2.field(blosc2.vlstring())
1084+
1085+
t = CTable(VLRow, new_data=[("hello",), ("world",)])
1086+
raw = t.note.array
1087+
assert isinstance(raw, _ScalarVarLenArray)
1088+
assert raw is t._cols["note"]
1089+
1090+
1091+
def test_column_array_list():
1092+
"""array on a list column returns the underlying ListArray."""
1093+
from blosc2.list_array import ListArray
1094+
1095+
t = CTable(
1096+
ArrayRow,
1097+
new_data=[
1098+
(1.0, "a", "foo", ["x", "y"]),
1099+
(2.0, "b", "bar", ["z"]),
1100+
],
1101+
)
1102+
raw = t.tags.array
1103+
assert isinstance(raw, ListArray)
1104+
assert raw is t._cols["tags"]
1105+
1106+
1107+
def test_column_array_computed_raises():
1108+
"""array raises AttributeError for computed (virtual) columns."""
1109+
t = CTable(Row, new_data=DATA20)
1110+
t.add_computed_column("double_score", lambda cols: cols["score"] * 2)
1111+
with pytest.raises(AttributeError, match="computed"):
1112+
_ = t.double_score.array
1113+
1114+
1115+
def test_column_array_bypasses_null_scan():
1116+
"""array returns values that include the raw sentinel, not NaN-substituted values."""
1117+
1118+
@dataclass
1119+
class NullRow:
1120+
x: float = blosc2.field(blosc2.float64(null_value=-1.0))
1121+
1122+
t = CTable(NullRow, new_data=[(1.0,), (-1.0,), (3.0,)])
1123+
raw = t.x.array[:]
1124+
# The raw array contains the sentinel as-is, not converted to NaN.
1125+
assert raw[1] == -1.0
1126+
1127+
10441128
if __name__ == "__main__":
10451129
pytest.main(["-v", __file__])

0 commit comments

Comments
 (0)