Skip to content

Commit

Permalink
[python] Add a run-time type check on contexts provided to `tiledbsom…
Browse files Browse the repository at this point in the history
…a.io` (#1297)

* [python] Add a run-time type check on contexts provided to tiledbsoma.io

* temp

* code-review feedback
  • Loading branch information
johnkerl authored Apr 27, 2023
1 parent 508c96c commit 24b57de
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 8 deletions.
3 changes: 2 additions & 1 deletion apis/python/src/tiledbsoma/_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from ._types import OpenTimestamp
from ._util import is_relative_uri, make_relative_path, uri_joinpath
from .options import SOMATileDBContext
from .options._soma_tiledb_context import _validate_soma_tiledb_context

# A collection can hold any sub-type of TileDBObject
CollectionElementType = TypeVar("CollectionElementType", bound=AnyTileDBObject)
Expand Down Expand Up @@ -107,7 +108,7 @@ def create(
Lifecycle:
Experimental.
"""
context = context or SOMATileDBContext()
context = _validate_soma_tiledb_context(context)
tiledb.group_create(uri=uri, ctx=context.tiledb_ctx)
handle = cls._wrapper_type.open(uri, "w", context, tiledb_timestamp)
cls._set_create_metadata(handle)
Expand Down
7 changes: 5 additions & 2 deletions apis/python/src/tiledbsoma/_common_nd_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
from . import _arrow_types, _util
from ._tiledb_array import TileDBArray
from ._types import OpenTimestamp
from .options._soma_tiledb_context import SOMATileDBContext
from .options._soma_tiledb_context import (
SOMATileDBContext,
_validate_soma_tiledb_context,
)
from .options._tiledb_create_options import TileDBCreateOptions


Expand Down Expand Up @@ -84,7 +87,7 @@ def create(
# else) sets extent to a not-power-of-two number like 999 or 1000 then the create fails if
# the upper limit is exactly 2**63-1.

context = context or SOMATileDBContext()
context = _validate_soma_tiledb_context(context)
schema = cls._build_tiledb_schema(
type,
shape,
Expand Down
3 changes: 2 additions & 1 deletion apis/python/src/tiledbsoma/_dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ._tiledb_array import TileDBArray
from ._types import NPFloating, NPInteger, OpenTimestamp, Slice, is_slice_of
from .options import SOMATileDBContext
from .options._soma_tiledb_context import _validate_soma_tiledb_context
from .options._tiledb_create_options import TileDBCreateOptions

_UNBATCHED = options.BatchSize()
Expand Down Expand Up @@ -199,7 +200,7 @@ def create(
Lifecycle:
Experimental.
"""
context = context or SOMATileDBContext()
context = _validate_soma_tiledb_context(context)
schema = _canonicalize_schema(schema, index_column_names)
tdb_schema = _build_tiledb_schema(
schema,
Expand Down
3 changes: 2 additions & 1 deletion apis/python/src/tiledbsoma/_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ._funcs import typeguard_ignore
from ._types import OpenTimestamp
from .options import SOMATileDBContext
from .options._soma_tiledb_context import _validate_soma_tiledb_context

_Obj = TypeVar("_Obj", bound="_tiledb_object.AnyTileDBObject")
_Wrapper = TypeVar("_Wrapper", bound=_tdb_handles.AnyWrapper)
Expand Down Expand Up @@ -109,7 +110,7 @@ def open(
Lifecycle:
Experimental.
"""
context = context or SOMATileDBContext()
context = _validate_soma_tiledb_context(context)
obj = _open_internal(_tdb_handles.open, uri, mode, context, tiledb_timestamp)
try:
if soma_type:
Expand Down
5 changes: 3 additions & 2 deletions apis/python/src/tiledbsoma/_tiledb_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ._types import OpenTimestamp
from ._util import check_type, ms_to_datetime
from .options import SOMATileDBContext
from .options._soma_tiledb_context import _validate_soma_tiledb_context

_WrapperType_co = TypeVar(
"_WrapperType_co", bound=_tdb_handles.AnyWrapper, covariant=True
Expand Down Expand Up @@ -79,7 +80,7 @@ def open(
Experimental.
"""
del platform_config # unused
context = context or SOMATileDBContext()
context = _validate_soma_tiledb_context(context)
handle = cls._wrapper_type.open(uri, mode, context, tiledb_timestamp)
return cls(
handle,
Expand Down Expand Up @@ -252,7 +253,7 @@ def exists(
Experimental.
"""
check_type("uri", uri, (str,))
context = context or SOMATileDBContext()
context = _validate_soma_tiledb_context(context)
try:
with cls._wrapper_type.open(uri, "r", context, tiledb_timestamp) as hdl:
md_type = hdl.metadata.get(_constants.SOMA_OBJECT_TYPE_METADATA_KEY)
Expand Down
7 changes: 6 additions & 1 deletion apis/python/src/tiledbsoma/io/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from .._tiledb_object import AnyTileDBObject, TileDBObject
from .._types import INGEST_MODES, IngestMode, NPNDArray, Path
from ..options import SOMATileDBContext
from ..options._soma_tiledb_context import _validate_soma_tiledb_context
from ..options._tiledb_create_options import TileDBCreateOptions
from . import conversions

Expand Down Expand Up @@ -118,7 +119,9 @@ def from_h5ad(
)

if isinstance(input_path, ad.AnnData):
raise TypeError("Input path is an AnnData object -- did you want from_anndata?")
raise TypeError("input path is an AnnData object -- did you want from_anndata?")

context = _validate_soma_tiledb_context(context)

s = _util.get_start_stamp()
logging.log_io(None, f"START Experiment.from_h5ad {input_path}")
Expand Down Expand Up @@ -202,6 +205,8 @@ def from_anndata(
"Second argument is not an AnnData object -- did you want from_h5ad?"
)

context = _validate_soma_tiledb_context(context)

# Without _at least_ an index, there is nothing to indicate the dimension indices.
if anndata.obs.index.empty or anndata.var.index.empty:
raise NotImplementedError("Empty AnnData.obs or AnnData.var unsupported.")
Expand Down
22 changes: 22 additions & 0 deletions apis/python/src/tiledbsoma/options/_soma_tiledb_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,25 @@ def _open_timestamp_ms(self, in_timestamp: Optional[OpenTimestamp]) -> int:
if self.timestamp_ms is not None:
return self.timestamp_ms
return int(time.time() * 1000)


def _validate_soma_tiledb_context(context: Any) -> SOMATileDBContext:
"""Returns the argument, as long as it's a ``SOMATileDBContext``, or a new
one if the argument is ``None``. While we already have static type-checking,
a few things are extra-important to have runtime validation on. Since it's
easy for users to pass a ``tiledb.Ctx`` when a ``SOMATileDBContext`` is
expected, we should offer a helpful redirect when they do.
"""

if context is None:
return SOMATileDBContext()

if isinstance(context, tiledb.Ctx):
raise TypeError(
"context is a tiledb.Ctx, not a SOMATileDBContext -- please wrap it in tiledbsoma.SOMATileDBContext(...)"
)

if not isinstance(context, SOMATileDBContext):
raise TypeError("context is not a SOMATileDBContext")

return context

0 comments on commit 24b57de

Please sign in to comment.