Repository: TileDB-Inc/TileDB-Py
Pull request #2139 (2025-01-10 22:10:20)

Fix `view_attr` not being respected by `__getitem__` and `subarray`
This PR fixes an issue where the `attr` argument in the `Array.open` method (internally stored as `view_attr`) was not respected during query operations. Consequently, the `Array.query` method ignored the specified attribute(s) and returned all attributes instead. The issue has been resolved by updating the `__getitem__` and `subarray` methods of both Sparse and Dense Arrays.

---

[sc-61659]


Patch:
diff --git a/HISTORY.md b/HISTORY.md
index 17ca58af..0af01d41 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,3 +1,33 @@
+# Release 0.33.3
+
+## Bug Fixes
+
+* Fix interaction with CloudArray interface of tiledb-cloud package @kounelisagis in https://github.com/TileDB-Inc/TileDB-Py/pull/2144
+
+## Improvements
+
+* Add cc alias for libtiledb to provide partial backward compatibility with existing usage of internal APIs by @kounelisagis in https://github.com/TileDB-Inc/TileDB-Py/pull/2145
+
+**Full Changelog**: https://github.com/TileDB-Inc/TileDB-Py/compare/0.33.2...0.33.3
+
+# Release 0.33.2
+
+## What's Changed
+
+Wheels are now available for aarch64 Linux on PyPI.
+
+## Bug Fixes
+
+* Explicitly set the numpy type passed in `QueryExperimental::set_data_buffer` by @kounelisagis in https://github.com/TileDB-Inc/TileDB-Py/pull/2128
+
+## Improvements
+
+* Enable aarch64 wheel builds by @dudoslav in https://github.com/TileDB-Inc/TileDB-Py/pull/2114
+* Rename default branch from `dev` to `main`, update associated references by @kounelisagis in https://github.com/TileDB-Inc/TileDB-Py/pull/2126
+* Fix/generalize schema evolution test added in #2083 by @kounelisagis in https://github.com/TileDB-Inc/TileDB-Py/pull/2116
+
+**Full Changelog**: https://github.com/TileDB-Inc/TileDB-Py/compare/0.33.1...0.33.2
+
 # Release 0.33.1
 
 ## Packaging Notes
diff --git a/tiledb/__init__.py b/tiledb/__init__.py
index 5c39b061..08f73aba 100644
--- a/tiledb/__init__.py
+++ b/tiledb/__init__.py
@@ -1,6 +1,7 @@
 import ctypes
 import os
 import sys
+import warnings
 
 if os.name == "posix":
     if sys.platform == "darwin":
@@ -107,6 +108,29 @@ from .vfs import VFS, FileIO
 __version__ = version.version
 group_create = Group.create
 
+# Create a proxy object to wrap libtiledb and provide a `cc` alias
+class CCProxy:
+    def __init__(self, module):
+        self._module = module
+
+    def __getattr__(self, name):
+        warnings.warn(
+            "`tiledb.cc` is deprecated. Please use `tiledb.libtiledb` instead.",
+        )
+        return getattr(self._module, name)
+
+    def __repr__(self):
+        warnings.warn(
+            "`tiledb.cc` is deprecated. Please use `tiledb.libtiledb` instead.",
+        )
+        return self._module.__repr__()
+
+
+cc = CCProxy(libtiledb)
+sys.modules["tiledb.cc"] = cc
+cc = cc
+del CCProxy
+
 # Note: we use a modified namespace packaging to allow continuity of existing TileDB-Py imports.
 #       Therefore, 'tiledb/__init__.py' must *only* exist in this package.
 #       Furthermore, in sub-packages, the `find_packages` helper will not work at the
diff --git a/tiledb/array.py b/tiledb/array.py
index 22d1fbd9..12919d91 100644
--- a/tiledb/array.py
+++ b/tiledb/array.py
@@ -416,17 +416,14 @@ class Array:
         if ctx is None:
             ctx = default_ctx()
 
-        from .dense_array import DenseArrayImpl
-        from .sparse_array import SparseArrayImpl
-
         tmp_array = preload_array(uri, mode, key, timestamp, ctx)
 
         if tmp_array._schema()._array_type == lt.ArrayType.SPARSE:
-            return SparseArrayImpl(
+            return tiledb.SparseArray(
                 uri, mode, key, timestamp, attr, ctx, preloaded_array=tmp_array
             )
         else:
-            return DenseArrayImpl(
+            return tiledb.DenseArray(
                 uri, mode, key, timestamp, attr, ctx, preloaded_array=tmp_array
             )
 
diff --git a/tiledb/dataframe_.py b/tiledb/dataframe_.py
index d030c4d8..e3c3a0f9 100644
--- a/tiledb/dataframe_.py
+++ b/tiledb/dataframe_.py
@@ -203,6 +203,9 @@ class ColumnInfo:
         if isinstance(dtype, str) and dtype == "ascii":
             return cls("ascii", var=True)
 
+        if isinstance(dtype, str) and dtype == "blob":
+            return cls(np.bytes_, var=True)
+
         dtype = pd_types.pandas_dtype(dtype)
         # Note: be careful if you rearrange the order of the following checks
 
diff --git a/tiledb/dense_array.py b/tiledb/dense_array.py
index b9050f31..2922c0d6 100644
--- a/tiledb/dense_array.py
+++ b/tiledb/dense_array.py
@@ -77,8 +77,7 @@ class DenseArrayImpl(Array):
 
         """
         if self.view_attr:
-            result = self.subarray(selection, attrs=(self.view_attr,))
-            return result[self.view_attr]
+            return self.subarray(selection)
 
         result = self.subarray(selection)
         for i in range(self.schema.nattr):
@@ -291,6 +290,8 @@ class DenseArrayImpl(Array):
             attr = self.schema.attr(0)
             if attr.isanon:
                 return out[attr._internal_name]
+        if self.view_attr is not None:
+            return out[self.view_attr]
         return out
 
     def _read_dense_subarray(
diff --git a/tiledb/highlevel.py b/tiledb/highlevel.py
index e9727a01..1986fe17 100644
--- a/tiledb/highlevel.py
+++ b/tiledb/highlevel.py
@@ -283,10 +283,10 @@ def vacuum(uri, config=None, ctx=None, timestamp=None):
     :raises TypeError: cannot convert `uri` to unicode string
     :raises: :py:exc:`tiledb.TileDBError`
 
-    This operation of this function is controlled by
-    the `"sm.vacuum.mode"` parameter, which accepts the values ``fragments``,
-    ``fragment_meta``, and ``array_meta``. Rather than passing the timestamp
-    into this function, it may be set by using `"sm.vacuum.timestamp_start"`and
+    This operation of this function is controlled by the `"sm.vacuum.mode"`
+    parameter, which accepts the values ``fragments``, ``fragment_meta``,
+    ``array_meta``, and ``commits``. Rather than passing the timestamp into
+    this function, it may be set by using `"sm.vacuum.timestamp_start"`and
     `"sm.vacuum.timestamp_end"` which takes in a time in UNIX seconds. If both
     are set then this function's `timestamp` argument will be used.
 
diff --git a/tiledb/sparse_array.py b/tiledb/sparse_array.py
index a44c5ba8..c76150eb 100644
--- a/tiledb/sparse_array.py
+++ b/tiledb/sparse_array.py
@@ -1,3 +1,4 @@
+import warnings
 from collections import OrderedDict
 
 import numpy as np
@@ -292,6 +293,9 @@ class SparseArrayImpl(Array):
         >>> # A[5.0:579.9]
 
         """
+        if self.view_attr is not None:
+            return self.subarray(selection)
+
         result = self.subarray(selection)
         for i in range(self.schema.nattr):
             attr = self.schema.attr(i)
@@ -518,7 +522,11 @@ class SparseArrayImpl(Array):
 
         attr_names = list()
 
-        if attrs is None:
+        if self.view_attr is not None:
+            if attrs is not None:
+                warnings.warn("view_attr is set, ignoring attrs parameter", UserWarning)
+            attr_names.extend(self.view_attr)
+        elif attrs is None:
             attr_names.extend(
                 self.schema.attr(i)._internal_name for i in range(self.schema.nattr)
             )


Test patch:
diff --git a/tiledb/tests/test_libtiledb.py b/tiledb/tests/test_libtiledb.py
index 4f5e7655..2447b1e9 100644
--- a/tiledb/tests/test_libtiledb.py
+++ b/tiledb/tests/test_libtiledb.py
@@ -23,6 +23,7 @@ from tiledb.datatypes import DataType
 from .common import (
     DiskTestCase,
     assert_captured,
+    assert_dict_arrays_equal,
     assert_subarrays_equal,
     assert_unordered_equal,
     fx_sparse_cell_order,  # noqa: F401
@@ -923,8 +924,8 @@ class DenseArrayTest(DiskTestCase):
         assert_ts((timestamps[2], None), A * 3)
         assert_ts((timestamps[2], None), A * 3)
 
-    def test_open_attr(self):
-        uri = self.path("test_open_attr")
+    def test_open_attr_dense(self):
+        uri = self.path("test_open_attr_dense")
         schema = tiledb.ArraySchema(
             domain=tiledb.Domain(
                 tiledb.Dim(name="dim0", dtype=np.uint32, domain=(1, 4))
@@ -949,6 +950,48 @@ class DenseArrayTest(DiskTestCase):
             assert_array_equal(A[:], np.array((1, 2, 3, 4)))
             assert list(A.multi_index[:].keys()) == ["x"]
 
+        with tiledb.open(uri, attr="x") as A:
+            q = A.query(cond="x <= 3")
+            expected = np.array([1, 2, 3, schema.attr("x").fill[0]])
+            assert_array_equal(q[:], expected)
+
+    def test_open_attr_sparse(self):
+        uri = self.path("test_open_attr_sparse")
+        schema = tiledb.ArraySchema(
+            domain=tiledb.Domain(
+                tiledb.Dim(name="dim0", dtype=np.uint32, domain=(1, 4))
+            ),
+            attrs=(
+                tiledb.Attr(name="x", dtype=np.int32),
+                tiledb.Attr(name="y", dtype=np.int32),
+            ),
+            sparse=True,
+        )
+        tiledb.Array.create(uri, schema)
+
+        with tiledb.open(uri, mode="w") as A:
+            A[[1, 2, 3, 4]] = {"x": np.array((1, 2, 3, 4)), "y": np.array((5, 6, 7, 8))}
+
+        with self.assertRaises(KeyError):
+            tiledb.open(uri, attr="z")
+
+        with self.assertRaises(KeyError):
+            tiledb.open(uri, attr="dim0")
+
+        with tiledb.open(uri, attr="x") as A:
+            expected = OrderedDict(
+                [("dim0", np.array([1, 2, 3, 4])), ("x", np.array([1, 2, 3, 4]))]
+            )
+            assert_dict_arrays_equal(A[:], expected)
+            assert list(A.multi_index[:].keys()) == ["dim0", "x"]
+
+        with tiledb.open(uri, attr="x") as A:
+            q = A.query(cond="x <= 3")
+            expected = OrderedDict(
+                [("dim0", np.array([1, 2, 3])), ("x", np.array([1, 2, 3]))]
+            )
+            assert_dict_arrays_equal(q[:], expected)
+
     def test_ncell_attributes(self):
         dom = tiledb.Domain(tiledb.Dim(domain=(0, 9), tile=10, dtype=int))
         attr = tiledb.Attr(dtype=[("", np.int32), ("", np.int32), ("", np.int32)])
diff --git a/tiledb/tests/test_pandas_dataframe.py b/tiledb/tests/test_pandas_dataframe.py
index f7374252..0b390b48 100644
--- a/tiledb/tests/test_pandas_dataframe.py
+++ b/tiledb/tests/test_pandas_dataframe.py
@@ -271,15 +271,17 @@ class TestColumnInfo(DiskTestCase):
                 {
                     "a": np.random.random_sample(20),
                     "b": [str(uuid.uuid4()) for _ in range(20)],
+                    "c": [np.random.bytes(10) for _ in range(20)],
                 }
             ),
             sparse=True,
-            index_dims=["a", "b"],
-            column_types={"a": np.float32, "b": np.bytes_},
+            index_dims=["a", "b", "c"],
+            column_types={"a": np.float32, "b": np.bytes_, "c": "blob"},
         )
         with tiledb.open(uri) as A:
             assert A.schema.domain.dim(0).dtype == np.float32
             assert A.schema.domain.dim(1).dtype == np.bytes_
+            assert A.schema.domain.dim(2).dtype == np.bytes_
 
     def test_apply_dtype_index_schema_only(self):
         uri = self.path("index_dtype_casted_dtype")