Skip to content

Commit

Permalink
[python, c++] Fix misleading DoesNotExistErrors in open factory (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenv authored Dec 12, 2024
1 parent 7330fbd commit a8349a5
Show file tree
Hide file tree
Showing 33 changed files with 526 additions and 190 deletions.
23 changes: 1 addition & 22 deletions apis/python/src/tiledbsoma/_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def create(
timestamp_ms = context._open_timestamp_ms(tiledb_timestamp)
clib.SOMAGroup.create(
uri=uri,
soma_type=wrapper._GROUP_WRAPPED_TYPE.__name__,
soma_type=wrapper._WRAPPED_TYPE.__name__,
ctx=context.native_context,
timestamp=(0, timestamp_ms),
)
Expand All @@ -122,27 +122,6 @@ def create(
except SOMAError as e:
raise map_exception_for_create(e, uri) from None

@classmethod
def open(
cls,
uri: str,
mode: options.OpenMode = "r",
*,
tiledb_timestamp: Optional[OpenTimestamp] = None,
context: Optional[SOMATileDBContext] = None,
platform_config: Optional[options.PlatformConfig] = None,
clib_type: Optional[str] = None,
) -> Self:
"""Opens this specific type of SOMA object."""
return super().open(
uri,
mode,
tiledb_timestamp=tiledb_timestamp,
context=context,
platform_config=platform_config,
clib_type="SOMAGroup",
)

# Subclass protocol to constrain which SOMA objects types may be set on a
# particular collection key. Used by Experiment and Measurement.
_subclass_constrained_soma_types: ClassVar[Dict[str, Tuple[str, ...]]] = {}
Expand Down
10 changes: 7 additions & 3 deletions apis/python/src/tiledbsoma/_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"""Exceptions.
"""

from typing import Union


class SOMAError(Exception):
"""Base error type for SOMA-specific exceptions.
Expand All @@ -25,16 +27,17 @@ class DoesNotExistError(SOMAError):
pass


def is_does_not_exist_error(e: RuntimeError) -> bool:
"""Given a RuntimeError, return true if it indicates the object does not exist
def is_does_not_exist_error(e: Union[RuntimeError, SOMAError]) -> bool:
"""Given a RuntimeError or SOMAError, return true if it indicates the object
does not exist
Lifecycle: Maturing.
Example:
try:
with tiledbsoma.open(uri):
...
except RuntimeError as e:
except (RuntimeError, SOMAError) as e:
if is_does_not_exist_error(e):
...
raise e
Expand All @@ -47,6 +50,7 @@ def is_does_not_exist_error(e: RuntimeError) -> bool:
or "Unrecognized array" in stre
or "HTTP code 401" in stre
or "HTTP code 404" in stre
or "[SOMAObject::open] " in stre
):
return True

Expand Down
27 changes: 1 addition & 26 deletions apis/python/src/tiledbsoma/_soma_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@
#
# Licensed under the MIT License.

from typing import Any, Optional, Tuple
from typing import Any, Tuple

import pyarrow as pa
from somacore import options
from typing_extensions import Self

from . import _tdb_handles

# This package's pybind11 code
from . import pytiledbsoma as clib # noqa: E402
from ._soma_object import SOMAObject
from ._types import OpenTimestamp
from .options._soma_tiledb_context import SOMATileDBContext


class SOMAArray(SOMAObject[_tdb_handles.SOMAArrayWrapper[Any]]):
Expand All @@ -27,27 +23,6 @@ class SOMAArray(SOMAObject[_tdb_handles.SOMAArrayWrapper[Any]]):

__slots__ = ()

@classmethod
def open(
cls,
uri: str,
mode: options.OpenMode = "r",
*,
tiledb_timestamp: Optional[OpenTimestamp] = None,
context: Optional[SOMATileDBContext] = None,
platform_config: Optional[options.PlatformConfig] = None,
clib_type: Optional[str] = None,
) -> Self:
"""Opens this specific type of SOMA object."""
return super().open(
uri,
mode,
tiledb_timestamp=tiledb_timestamp,
context=context,
platform_config=platform_config,
clib_type="SOMAArray",
)

@property
def schema(self) -> pa.Schema:
"""Returns data schema, in the form of an
Expand Down
6 changes: 5 additions & 1 deletion apis/python/src/tiledbsoma/_soma_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ def open(
del platform_config # unused
context = _validate_soma_tiledb_context(context)
handle = _tdb_handles.open(
uri, mode, context, tiledb_timestamp, clib_type=clib_type
uri,
mode,
context,
tiledb_timestamp,
clib_type=cls._wrapper_type._WRAPPED_TYPE.__name__,
)
if not isinstance(handle, cls._wrapper_type):
handle = cls._wrapper_type.open(uri, mode, context, tiledb_timestamp)
Expand Down
75 changes: 45 additions & 30 deletions apis/python/src/tiledbsoma/_tdb_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
_RawHdl_co = TypeVar("_RawHdl_co", bound=RawHandle, covariant=True)
"""A raw TileDB object. Covariant because Handles are immutable enough."""

_SOMAObjectType = TypeVar("_SOMAObjectType", bound=clib.SOMAObject)


def open(
uri: str,
Expand All @@ -70,16 +72,35 @@ def open(

timestamp_ms = context._open_timestamp_ms(timestamp)

soma_object = clib.SOMAObject.open(
uri=uri,
mode=open_mode,
context=context.native_context,
timestamp=(0, timestamp_ms),
clib_type=clib_type,
)
_type_to_open = {
"somaarray": clib.SOMAArray.open,
"somagroup": clib.SOMAGroup.open,
"somadataframe": clib.SOMADataFrame.open,
"somapointclouddataframe": clib.SOMAPointCloudDataFrame.open,
"somadensendarray": clib.SOMADenseNDArray.open,
"somasparsendarray": clib.SOMASparseNDArray.open,
"somacollection": clib.SOMACollection.open,
"somaexperiment": clib.SOMAExperiment.open,
"somameasurement": clib.SOMAMeasurement.open,
"somascene": clib.SOMAScene.open,
"somamultiscaleimage": clib.SOMAMultiscaleImage.open,
None: clib.SOMAObject.open,
}

if not soma_object:
raise DoesNotExistError(f"{uri!r} does not exist")
if clib_type is not None:
clib_type = clib_type.lower()

try:
soma_object = _type_to_open[clib_type](
uri=uri,
mode=open_mode,
context=context.native_context,
timestamp=(0, timestamp_ms),
)
except (RuntimeError, SOMAError) as tdbe:
if is_does_not_exist_error(tdbe):
raise DoesNotExistError(tdbe) from tdbe
raise SOMAError(tdbe) from tdbe

_type_to_class = {
"somadataframe": DataFrameWrapper,
Expand Down Expand Up @@ -265,13 +286,10 @@ def from_soma_group_entry(cls, obj: Tuple[str, str]) -> "GroupEntry":
raise SOMAError(f"internal error: unknown object type {uri}")


_GrpType = TypeVar("_GrpType", bound=clib.SOMAGroup)


class SOMAGroupWrapper(Wrapper[_GrpType]):
class SOMAGroupWrapper(Wrapper[_SOMAObjectType]):
"""Base class for Pybind11 SOMAGroupWrapper handles."""

_GROUP_WRAPPED_TYPE: Type[_GrpType]
_WRAPPED_TYPE: Type[_SOMAObjectType]

clib_type = "SOMAGroup"

Expand All @@ -284,7 +302,7 @@ def _opener(
timestamp: int,
) -> clib.SOMAGroup:
open_mode = clib.OpenMode.read if mode == "r" else clib.OpenMode.write
return cls._GROUP_WRAPPED_TYPE.open(
return cls._WRAPPED_TYPE.open(
uri,
mode=open_mode,
context=context.native_context,
Expand All @@ -310,40 +328,37 @@ def members(self) -> Dict[str, Tuple[str, str]]:
class CollectionWrapper(SOMAGroupWrapper[clib.SOMACollection]):
"""Wrapper around a Pybind11 CollectionWrapper handle."""

_GROUP_WRAPPED_TYPE = clib.SOMACollection
_WRAPPED_TYPE = clib.SOMACollection


class ExperimentWrapper(SOMAGroupWrapper[clib.SOMAExperiment]):
"""Wrapper around a Pybind11 ExperimentWrapper handle."""

_GROUP_WRAPPED_TYPE = clib.SOMAExperiment
_WRAPPED_TYPE = clib.SOMAExperiment


class MeasurementWrapper(SOMAGroupWrapper[clib.SOMAMeasurement]):
"""Wrapper around a Pybind11 MeasurementWrapper handle."""

_GROUP_WRAPPED_TYPE = clib.SOMAMeasurement
_WRAPPED_TYPE = clib.SOMAMeasurement


class MultiscaleImageWrapper(SOMAGroupWrapper[clib.SOMAMultiscaleImage]):
"""Wrapper around a Pybind11 MultiscaleImage handle."""

_GROUP_WRAPPED_TYPE = clib.SOMAMultiscaleImage
_WRAPPED_TYPE = clib.SOMAMultiscaleImage


class SceneWrapper(SOMAGroupWrapper[clib.SOMAScene]):
"""Wrapper around a Pybind11 SceneWrapper handle."""

_GROUP_WRAPPED_TYPE = clib.SOMAScene


_ArrType = TypeVar("_ArrType", bound=clib.SOMAArray)
_WRAPPED_TYPE = clib.SOMAScene


class SOMAArrayWrapper(Wrapper[_ArrType]):
class SOMAArrayWrapper(Wrapper[_SOMAObjectType]):
"""Base class for Pybind11 SOMAArrayWrapper handles."""

_ARRAY_WRAPPED_TYPE: Type[_ArrType]
_WRAPPED_TYPE: Type[_SOMAObjectType]

clib_type = "SOMAArray"

Expand All @@ -357,7 +372,7 @@ def _opener(
) -> clib.SOMAArray:
open_mode = clib.OpenMode.read if mode == "r" else clib.OpenMode.write

return cls._ARRAY_WRAPPED_TYPE.open(
return cls._WRAPPED_TYPE.open(
uri,
mode=open_mode,
context=context.native_context,
Expand Down Expand Up @@ -517,7 +532,7 @@ def can_change_domain(
class DataFrameWrapper(SOMAArrayWrapper[clib.SOMADataFrame]):
"""Wrapper around a Pybind11 SOMADataFrame handle."""

_ARRAY_WRAPPED_TYPE = clib.SOMADataFrame
_WRAPPED_TYPE = clib.SOMADataFrame

@property
def count(self) -> int:
Expand Down Expand Up @@ -607,7 +622,7 @@ def can_change_domain(
class PointCloudDataFrameWrapper(SOMAArrayWrapper[clib.SOMAPointCloudDataFrame]):
"""Wrapper around a Pybind11 SOMAPointCloudDataFrame handle."""

_ARRAY_WRAPPED_TYPE = clib.SOMAPointCloudDataFrame
_WRAPPED_TYPE = clib.SOMAPointCloudDataFrame

@property
def count(self) -> int:
Expand All @@ -620,7 +635,7 @@ def write(self, values: pa.RecordBatch) -> None:
class DenseNDArrayWrapper(SOMAArrayWrapper[clib.SOMADenseNDArray]):
"""Wrapper around a Pybind11 DenseNDArrayWrapper handle."""

_ARRAY_WRAPPED_TYPE = clib.SOMADenseNDArray
_WRAPPED_TYPE = clib.SOMADenseNDArray

@property
def tiledbsoma_has_upgraded_shape(self) -> bool:
Expand Down Expand Up @@ -653,7 +668,7 @@ def tiledbsoma_can_upgrade_shape(
class SparseNDArrayWrapper(SOMAArrayWrapper[clib.SOMASparseNDArray]):
"""Wrapper around a Pybind11 SparseNDArrayWrapper handle."""

_ARRAY_WRAPPED_TYPE = clib.SOMASparseNDArray
_WRAPPED_TYPE = clib.SOMASparseNDArray

@property
def nnz(self) -> int:
Expand Down
44 changes: 40 additions & 4 deletions apis/python/src/tiledbsoma/soma_collection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,28 @@ void load_soma_collection(py::module& m) {
.def("get", &SOMACollection::get);

py::class_<SOMAExperiment, SOMACollection, SOMAGroup, SOMAObject>(
m, "SOMAExperiment");
m, "SOMAExperiment")
.def_static(
"open",
&SOMAExperiment::open,
"uri"_a,
py::kw_only(),
"mode"_a,
"context"_a,
"timestamp"_a = py::none(),
py::call_guard<py::gil_scoped_release>());

py::class_<SOMAMeasurement, SOMACollection, SOMAGroup, SOMAObject>(
m, "SOMAMeasurement");
m, "SOMAMeasurement")
.def_static(
"open",
&SOMAMeasurement::open,
"uri"_a,
py::kw_only(),
"mode"_a,
"context"_a,
"timestamp"_a = py::none(),
py::call_guard<py::gil_scoped_release>());

py::class_<SOMAScene, SOMACollection, SOMAGroup, SOMAObject>(m, "SOMAScene")
.def_static(
Expand All @@ -91,9 +109,27 @@ void load_soma_collection(py::module& m) {
py::kw_only(),
"ctx"_a,
"uri"_a,
"timestamp"_a = py::none());
"timestamp"_a = py::none())
.def_static(
"open",
&SOMAScene::open,
"uri"_a,
py::kw_only(),
"mode"_a,
"context"_a,
"timestamp"_a = py::none(),
py::call_guard<py::gil_scoped_release>());

py::class_<SOMAMultiscaleImage, SOMACollection, SOMAGroup, SOMAObject>(
m, "SOMAMultiscaleImage");
m, "SOMAMultiscaleImage")
.def_static(
"open",
&SOMAMultiscaleImage::open,
"uri"_a,
py::kw_only(),
"mode"_a,
"context"_a,
"timestamp"_a = py::none(),
py::call_guard<py::gil_scoped_release>());
}
} // namespace libtiledbsomacpp
Loading

0 comments on commit a8349a5

Please sign in to comment.