diff --git a/apis/python/setup.py b/apis/python/setup.py index 6615b3d44c..97b58773cf 100644 --- a/apis/python/setup.py +++ b/apis/python/setup.py @@ -13,6 +13,8 @@ # Based on ideas from https://github.com/pybind/cmake_example # The `bld` script here is reused for pip install, CI, and local builds. +from __future__ import annotations + import ctypes import os import pathlib @@ -20,7 +22,6 @@ import shutil import subprocess import sys -from typing import Optional import setuptools.command.bdist_wheel import setuptools.command.build_ext @@ -43,8 +44,8 @@ # tiledb_dir and tiledbsoma_dir may be specified by either environment variable # or command-line argument. If both are provided, the latter wins. -tiledb_dir: Optional[pathlib.Path] = None -tiledbsoma_dir: Optional[pathlib.Path] = None +tiledb_dir: pathlib.Path | None = None +tiledbsoma_dir: pathlib.Path | None = None no_tiledb_dep: bool = False args = sys.argv[:] diff --git a/apis/python/src/tiledbsoma/_collection.py b/apis/python/src/tiledbsoma/_collection.py index c7eac3e6f3..cb2cd1e200 100644 --- a/apis/python/src/tiledbsoma/_collection.py +++ b/apis/python/src/tiledbsoma/_collection.py @@ -13,7 +13,6 @@ Callable, ClassVar, Dict, - Optional, Tuple, Type, TypeVar, @@ -67,9 +66,9 @@ def create( cls, uri: str, *, - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: """Creates and opens a new SOMA collection in storage. @@ -147,8 +146,8 @@ def add_new_collection( key: str, kind: None = None, *, - uri: Optional[str] = ..., - platform_config: Optional[options.PlatformConfig] = ..., + uri: str | None = ..., + platform_config: options.PlatformConfig | None = ..., ) -> "Collection[AnySOMAObject]": ... @overload @@ -157,17 +156,17 @@ def add_new_collection( key: str, kind: Type[_Coll], *, - uri: Optional[str] = ..., - platform_config: Optional[options.PlatformConfig] = ..., + uri: str | None = ..., + platform_config: options.PlatformConfig | None = ..., ) -> _Coll: ... def add_new_collection( self, key: str, - kind: Optional[Type[CollectionBase]] = None, # type: ignore[type-arg] + kind: Type[CollectionBase] | None = None, # type: ignore[type-arg] *, - uri: Optional[str] = None, - platform_config: Optional[options.PlatformConfig] = None, + uri: str | None = None, + platform_config: options.PlatformConfig | None = None, ) -> AnyTileDBCollection: """Adds a new sub-collection to this collection. @@ -226,7 +225,7 @@ def add_new_collection( DataFrame.create, exclude=("context", "tiledb_timestamp") ) def add_new_dataframe( - self, key: str, *, uri: Optional[str] = None, **kwargs: Any + self, key: str, *, uri: str | None = None, **kwargs: Any ) -> DataFrame: """Adds a new DataFrame to this collection. @@ -269,7 +268,7 @@ def add_new_dataframe( @_funcs.forwards_kwargs_to(NDArray.create, exclude=("context", "tiledb_timestamp")) def _add_new_ndarray( - self, cls: Type[_NDArr], key: str, *, uri: Optional[str] = None, **kwargs: Any + self, cls: Type[_NDArr], key: str, *, uri: str | None = None, **kwargs: Any ) -> _NDArr: """Internal implementation of common NDArray-adding operations.""" return self._add_new_element( @@ -361,7 +360,7 @@ def _add_new_element( key: str, kind: Type[_TDBO], factory: Callable[[str], _TDBO], - user_uri: Optional[str], + user_uri: str | None, ) -> _TDBO: """Handles the common parts of adding new elements. diff --git a/apis/python/src/tiledbsoma/_common_nd_array.py b/apis/python/src/tiledbsoma/_common_nd_array.py index 9b28ce5b3c..a96996509d 100644 --- a/apis/python/src/tiledbsoma/_common_nd_array.py +++ b/apis/python/src/tiledbsoma/_common_nd_array.py @@ -5,7 +5,9 @@ """Common code shared by both NDArray implementations.""" -from typing import Optional, Sequence, Tuple, Union, cast +from __future__ import annotations + +from typing import Sequence, Tuple, Union, cast import pyarrow as pa import somacore @@ -32,9 +34,9 @@ def create( *, type: pa.DataType, shape: Sequence[Union[int, None]], - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: """Creates a SOMA ``NDArray`` at the given URI. @@ -154,7 +156,7 @@ def tiledbsoma_has_upgraded_shape(self) -> bool: def _dim_capacity_and_extent( cls, dim_name: str, - dim_shape: Optional[int], + dim_shape: int | None, ndim: int, create_options: TileDBCreateOptions, ) -> Tuple[int, int]: diff --git a/apis/python/src/tiledbsoma/_dataframe.py b/apis/python/src/tiledbsoma/_dataframe.py index 973b3e01d7..50721fd1ec 100644 --- a/apis/python/src/tiledbsoma/_dataframe.py +++ b/apis/python/src/tiledbsoma/_dataframe.py @@ -6,12 +6,14 @@ """ Implementation of a SOMA DataFrame """ + +from __future__ import annotations + import inspect from typing import ( Any, Dict, List, - Optional, Sequence, Tuple, Union, @@ -151,10 +153,10 @@ def create( *, schema: pa.Schema, index_column_names: Sequence[str] = (SOMA_JOINID,), - domain: Optional[Domain] = None, - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + domain: Domain | None = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> "DataFrame": """Creates the data structure on disk/S3/cloud. @@ -407,7 +409,7 @@ def count(self) -> int: return cast(DataFrameWrapper, self._handle).count @property - def _maybe_soma_joinid_shape(self) -> Optional[int]: + def _maybe_soma_joinid_shape(self) -> int | None: """An internal helper method that returns the shape value along the ``soma_joinid`` index column, if the ``DataFrame has one, else ``None``. @@ -419,7 +421,7 @@ def _maybe_soma_joinid_shape(self) -> Optional[int]: return self._handle.maybe_soma_joinid_shape @property - def _maybe_soma_joinid_maxshape(self) -> Optional[int]: + def _maybe_soma_joinid_maxshape(self) -> int | None: """An internal helper method that returns the maxshape value along the ``soma_joinid`` index column, if the ``DataFrame has one, else ``None``. @@ -657,13 +659,13 @@ def __len__(self) -> int: def read( self, coords: options.SparseDFCoords = (), - column_names: Optional[Sequence[str]] = None, + column_names: Sequence[str] | None = None, *, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, + value_filter: str | None = None, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, - platform_config: Optional[options.PlatformConfig] = None, + partitions: options.ReadPartitions | None = None, + platform_config: options.PlatformConfig | None = None, ) -> TableReadIter: """Reads a user-defined subset of data, addressed by the dataframe indexing columns, optionally filtered, and return results as one or more `Arrow tables `_. @@ -732,7 +734,7 @@ def read( ) def write( - self, values: pa.Table, platform_config: Optional[options.PlatformConfig] = None + self, values: pa.Table, platform_config: options.PlatformConfig | None = None ) -> Self: """Writes an `Arrow table `_ to the persistent object. As duplicate index values are not allowed, index values already diff --git a/apis/python/src/tiledbsoma/_dense_nd_array.py b/apis/python/src/tiledbsoma/_dense_nd_array.py index ef65f7c9d9..5920f9d2f4 100644 --- a/apis/python/src/tiledbsoma/_dense_nd_array.py +++ b/apis/python/src/tiledbsoma/_dense_nd_array.py @@ -7,7 +7,9 @@ Implementation of SOMA DenseNDArray. """ -from typing import List, Optional, Sequence, Tuple, Union +from __future__ import annotations + +from typing import List, Sequence, Tuple, Union import numpy as np import pyarrow as pa @@ -93,9 +95,9 @@ def create( *, type: pa.DataType, shape: Sequence[Union[int, None]], - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: context = _validate_soma_tiledb_context(context) @@ -173,8 +175,8 @@ def read( coords: options.DenseNDCoords = (), *, result_order: options.ResultOrderStr = somacore.ResultOrder.ROW_MAJOR, - partitions: Optional[options.ReadPartitions] = None, - platform_config: Optional[options.PlatformConfig] = None, + partitions: options.ReadPartitions | None = None, + platform_config: options.PlatformConfig | None = None, ) -> pa.Tensor: """Reads a user-defined dense slice of the array and return as an Arrow ``Tensor``. @@ -262,7 +264,7 @@ def write( coords: options.DenseNDCoords, values: pa.Tensor, *, - platform_config: Optional[options.PlatformConfig] = None, + platform_config: options.PlatformConfig | None = None, ) -> Self: """Writes a subarray, defined by ``coords`` and ``values``. Will overwrite existing values in the array. @@ -326,7 +328,7 @@ def write( def _dim_capacity_and_extent( cls, dim_name: str, - dim_shape: Optional[int], + dim_shape: int | None, ndim: int, create_options: TileDBCreateOptions, ) -> Tuple[int, int]: diff --git a/apis/python/src/tiledbsoma/_eager_iter.py b/apis/python/src/tiledbsoma/_eager_iter.py index c42e52c1ab..35f3655785 100644 --- a/apis/python/src/tiledbsoma/_eager_iter.py +++ b/apis/python/src/tiledbsoma/_eager_iter.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from concurrent import futures -from typing import Iterator, Optional, TypeVar +from typing import Iterator, TypeVar _T = TypeVar("_T") @@ -8,7 +10,7 @@ class EagerIterator(Iterator[_T]): def __init__( self, iterator: Iterator[_T], - pool: Optional[futures.Executor] = None, + pool: futures.Executor | None = None, ): super().__init__() self.iterator = iterator diff --git a/apis/python/src/tiledbsoma/_experiment.py b/apis/python/src/tiledbsoma/_experiment.py index dcd297d29e..16aa2533ff 100644 --- a/apis/python/src/tiledbsoma/_experiment.py +++ b/apis/python/src/tiledbsoma/_experiment.py @@ -5,8 +5,10 @@ """Implementation of a SOMA Experiment. """ + +from __future__ import annotations + import functools -from typing import Optional from somacore import experiment, query @@ -81,8 +83,8 @@ def axis_query( # type: ignore self, measurement_name: str, *, - obs_query: Optional[query.AxisQuery] = None, - var_query: Optional[query.AxisQuery] = None, + obs_query: query.AxisQuery | None = None, + var_query: query.AxisQuery | None = None, ) -> ExperimentAxisQuery: """Creates an axis query over this experiment. Lifecycle: Maturing. diff --git a/apis/python/src/tiledbsoma/_factory.py b/apis/python/src/tiledbsoma/_factory.py index bf5ec7d2f5..508f6d4e26 100644 --- a/apis/python/src/tiledbsoma/_factory.py +++ b/apis/python/src/tiledbsoma/_factory.py @@ -7,10 +7,11 @@ Collection. """ +from __future__ import annotations + from typing import ( Callable, Dict, - Optional, Type, TypeVar, Union, @@ -55,9 +56,9 @@ def open( uri: str, mode: options.OpenMode = ..., *, - soma_type: Optional[str] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + soma_type: str | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> AnySOMAObject: ... @@ -67,8 +68,8 @@ def open( mode: options.OpenMode, *, soma_type: Type[_Obj], - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> _Obj: ... @@ -78,8 +79,8 @@ def open( mode: options.OpenMode = "r", *, soma_type: Union[Type[SOMAObject], str, None] = None, # type: ignore[type-arg] - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> AnySOMAObject: """Opens a TileDB SOMA object. @@ -145,12 +146,12 @@ def open( def _open_internal( opener: Callable[ - [str, options.OpenMode, SOMATileDBContext, Optional[OpenTimestamp]], _Wrapper + [str, options.OpenMode, SOMATileDBContext, OpenTimestamp | None], _Wrapper ], uri: str, mode: options.OpenMode, context: SOMATileDBContext, - timestamp: Optional[OpenTimestamp], + timestamp: OpenTimestamp | None, ) -> SOMAObject[_Wrapper]: """Lower-level open function for internal use only.""" handle = opener(uri, mode, context, timestamp) diff --git a/apis/python/src/tiledbsoma/_geometry_dataframe.py b/apis/python/src/tiledbsoma/_geometry_dataframe.py index 4d984ef966..fff021523d 100644 --- a/apis/python/src/tiledbsoma/_geometry_dataframe.py +++ b/apis/python/src/tiledbsoma/_geometry_dataframe.py @@ -6,8 +6,10 @@ Implementation of a SOMA Geometry DataFrame """ +from __future__ import annotations + import warnings -from typing import Any, Optional, Sequence, Tuple, Union +from typing import Any, Sequence, Tuple, Union import pyarrow as pa import somacore @@ -45,10 +47,10 @@ def create( *, schema: pa.Schema, coordinate_space: Union[Sequence[str], CoordinateSpace] = ("x", "y"), - domain: Optional[Domain] = None, - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + domain: Domain | None = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: """Creates a new ``GeometryDataFrame`` at the given URI. @@ -91,13 +93,13 @@ def create( def read( self, coords: options.SparseDFCoords = (), - column_names: Optional[Sequence[str]] = None, + column_names: Sequence[str] | None = None, *, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, + partitions: options.ReadPartitions | None = None, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, - platform_config: Optional[options.PlatformConfig] = None, + value_filter: str | None = None, + platform_config: options.PlatformConfig | None = None, ) -> TableReadIter: """Reads a user-defined slice of data into Arrow tables. @@ -124,16 +126,16 @@ def read( def read_spatial_region( self, - region: Optional[options.SpatialRegion] = None, - column_names: Optional[Sequence[str]] = None, + region: options.SpatialRegion | None = None, + column_names: Sequence[str] | None = None, *, - region_transform: Optional[CoordinateTransform] = None, - region_coord_space: Optional[CoordinateSpace] = None, + region_transform: CoordinateTransform | None = None, + region_coord_space: CoordinateSpace | None = None, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, + partitions: options.ReadPartitions | None = None, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, - platform_config: Optional[options.PlatformConfig] = None, + value_filter: str | None = None, + platform_config: options.PlatformConfig | None = None, ) -> somacore.SpatialRead[somacore.ReadIter[pa.Table]]: """Reads data intersecting an user-defined region of space into a :class:`SpatialRead` with data in Arrow tables. @@ -174,7 +176,7 @@ def write( self, values: Union[pa.RecordBatch, pa.Table], *, - platform_config: Optional[options.PlatformConfig] = None, + platform_config: options.PlatformConfig | None = None, ) -> Self: """Writes the data from an Arrow table to the persistent object. diff --git a/apis/python/src/tiledbsoma/_indexer.py b/apis/python/src/tiledbsoma/_indexer.py index 0b031720bb..7d0649d2a4 100644 --- a/apis/python/src/tiledbsoma/_indexer.py +++ b/apis/python/src/tiledbsoma/_indexer.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List, Optional, Union +from typing import List, Union import numpy as np import numpy.typing as npt @@ -25,7 +25,7 @@ def tiledbsoma_build_index( - data: IndexerDataType, *, context: Optional[SOMATileDBContext] = None + data: IndexerDataType, *, context: SOMATileDBContext | None = None ) -> IndexLike: """Initialize re-indexer for provided indices (deprecated). @@ -52,7 +52,7 @@ class IntIndexer: """ def __init__( - self, data: IndexerDataType, *, context: Optional[SOMATileDBContext] = None + self, data: IndexerDataType, *, context: SOMATileDBContext | None = None ): """Initialize re-indexer for provided indices. diff --git a/apis/python/src/tiledbsoma/_multiscale_image.py b/apis/python/src/tiledbsoma/_multiscale_image.py index d92b4d2d03..4d4a4d466c 100644 --- a/apis/python/src/tiledbsoma/_multiscale_image.py +++ b/apis/python/src/tiledbsoma/_multiscale_image.py @@ -7,9 +7,11 @@ Implementation of a SOMA MultiscaleImage. """ +from __future__ import annotations + import json import warnings -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Dict, List, Sequence, Tuple, Union import attrs import pyarrow as pa @@ -119,16 +121,16 @@ def create( type: pa.DataType, level_shape: Sequence[int], level_key: str = "level0", - level_uri: Optional[str] = None, + level_uri: str | None = None, coordinate_space: Union[Sequence[str], CoordinateSpace] = ( "x", "y", ), - data_axis_order: Optional[Sequence[str]] = None, + data_axis_order: Sequence[str] | None = None, has_channel_axis: bool = True, - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: """Creates a new ``MultiscaleImage`` at the given URI. @@ -313,7 +315,7 @@ def add_new_level( self, key: str, *, - uri: Optional[str] = None, + uri: str | None = None, shape: Sequence[int], **kwargs: Any, ) -> DenseNDArray: @@ -389,7 +391,7 @@ def set( key: str, value: DenseNDArray, *, - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, ) -> Self: """Sets a new level in the multi-scale image to be an existing SOMA :class:`DenseNDArray`. @@ -422,14 +424,14 @@ def set( def read_spatial_region( self, level: Union[int, str], - region: Optional[options.SpatialRegion] = None, + region: options.SpatialRegion | None = None, *, channel_coords: options.DenseCoord = None, - region_transform: Optional[CoordinateTransform] = None, - region_coord_space: Optional[CoordinateSpace] = None, + region_transform: CoordinateTransform | None = None, + region_coord_space: CoordinateSpace | None = None, result_order: options.ResultOrderStr = options.ResultOrder.ROW_MAJOR, - data_axis_order: Optional[Sequence[str]] = None, - platform_config: Optional[options.PlatformConfig] = None, + data_axis_order: Sequence[str] | None = None, + platform_config: options.PlatformConfig | None = None, ) -> somacore.SpatialRead[pa.Tensor]: """Reads a user-defined spatial region from a specific level of the ``MultiscaleImage``. diff --git a/apis/python/src/tiledbsoma/_point_cloud_dataframe.py b/apis/python/src/tiledbsoma/_point_cloud_dataframe.py index df5bd44baa..fa35123435 100644 --- a/apis/python/src/tiledbsoma/_point_cloud_dataframe.py +++ b/apis/python/src/tiledbsoma/_point_cloud_dataframe.py @@ -6,8 +6,10 @@ Implementation of a SOMA Point Cloud DataFrame """ +from __future__ import annotations + import warnings -from typing import Any, Optional, Sequence, Tuple, Union, cast +from typing import Any, Sequence, Tuple, Union, cast import pyarrow as pa import somacore @@ -69,10 +71,10 @@ def create( *, schema: pa.Schema, coordinate_space: Union[Sequence[str], CoordinateSpace] = ("x", "y"), - domain: Optional[Domain] = None, - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + domain: Domain | None = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: """Creates a new ``PointCloudDataFrame`` at the given URI. @@ -118,7 +120,7 @@ def create( """ warnings.warn(SPATIAL_DISCLAIMER) - axis_dtype: Optional[pa.DataType] = None + axis_dtype: pa.DataType | None = None if not isinstance(coordinate_space, CoordinateSpace): coordinate_space = CoordinateSpace.from_axis_names(coordinate_space) index_column_names = coordinate_space.axis_names @@ -298,13 +300,13 @@ def count(self) -> int: def read( self, coords: options.SparseDFCoords = (), - column_names: Optional[Sequence[str]] = None, + column_names: Sequence[str] | None = None, *, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, + partitions: options.ReadPartitions | None = None, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, - platform_config: Optional[options.PlatformConfig] = None, + value_filter: str | None = None, + platform_config: options.PlatformConfig | None = None, ) -> TableReadIter: """Reads a user-defined slice of data into Arrow tables. @@ -343,16 +345,16 @@ def read( def read_spatial_region( self, - region: Optional[options.SpatialRegion] = None, - column_names: Optional[Sequence[str]] = None, + region: options.SpatialRegion | None = None, + column_names: Sequence[str] | None = None, *, - region_transform: Optional[CoordinateTransform] = None, - region_coord_space: Optional[CoordinateSpace] = None, + region_transform: CoordinateTransform | None = None, + region_coord_space: CoordinateSpace | None = None, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, + partitions: options.ReadPartitions | None = None, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, - platform_config: Optional[options.PlatformConfig] = None, + value_filter: str | None = None, + platform_config: options.PlatformConfig | None = None, ) -> somacore.SpatialRead[somacore.ReadIter[pa.Table]]: """Reads data intersecting a user-defined region of space into a :class:`SpatialRead` with data in Arrow tables. @@ -444,7 +446,7 @@ def write( self, values: Union[pa.RecordBatch, pa.Table], *, - platform_config: Optional[options.PlatformConfig] = None, + platform_config: options.PlatformConfig | None = None, ) -> Self: """Writes the data from an Arrow table to the persistent object. diff --git a/apis/python/src/tiledbsoma/_query.py b/apis/python/src/tiledbsoma/_query.py index 074ce4bb07..0352855587 100644 --- a/apis/python/src/tiledbsoma/_query.py +++ b/apis/python/src/tiledbsoma/_query.py @@ -5,6 +5,9 @@ """Implementation of a SOMA Experiment. """ + +from __future__ import annotations + import enum import warnings from concurrent.futures import ThreadPoolExecutor @@ -15,7 +18,6 @@ Dict, Literal, Mapping, - Optional, Protocol, Sequence, TypeVar, @@ -120,8 +122,8 @@ class AxisIndexer(query.AxisIndexer): query: "ExperimentAxisQuery" _index_factory: IndexFactory - _cached_obs: Optional[IndexLike] = None - _cached_var: Optional[IndexLike] = None + _cached_obs: IndexLike | None = None + _cached_var: IndexLike | None = None @property def _obs_index(self) -> IndexLike: @@ -243,11 +245,11 @@ def __init__( def obs( self, *, - column_names: Optional[Sequence[str]] = None, + column_names: Sequence[str] | None = None, batch_size: BatchSize = BatchSize(), - partitions: Optional[ReadPartitions] = None, + partitions: ReadPartitions | None = None, result_order: ResultOrderStr = _RO_AUTO, - platform_config: Optional[PlatformConfig] = None, + platform_config: PlatformConfig | None = None, ) -> ReadIter[pa.Table]: """Returns ``obs`` as an `Arrow table `_ @@ -269,11 +271,11 @@ def obs( def var( self, *, - column_names: Optional[Sequence[str]] = None, + column_names: Sequence[str] | None = None, batch_size: BatchSize = BatchSize(), - partitions: Optional[ReadPartitions] = None, + partitions: ReadPartitions | None = None, result_order: ResultOrderStr = _RO_AUTO, - platform_config: Optional[PlatformConfig] = None, + platform_config: PlatformConfig | None = None, ) -> ReadIter[pa.Table]: """Returns ``var`` as an `Arrow table `_ @@ -335,9 +337,9 @@ def X( layer_name: str, *, batch_size: BatchSize = BatchSize(), - partitions: Optional[ReadPartitions] = None, + partitions: ReadPartitions | None = None, result_order: ResultOrderStr = _RO_AUTO, - platform_config: Optional[PlatformConfig] = None, + platform_config: PlatformConfig | None = None, ) -> SparseRead: """Returns an ``X`` layer as a sparse read. @@ -457,7 +459,7 @@ def to_anndata( self, X_name: str, *, - column_names: Optional[AxisColumnNames] = None, + column_names: AxisColumnNames | None = None, X_layers: Sequence[str] = (), obsm_layers: Sequence[str] = (), obsp_layers: Sequence[str] = (), @@ -490,7 +492,7 @@ def to_spatialdata( # type: ignore[no-untyped-def] self, X_name: str, *, - column_names: Optional[AxisColumnNames] = None, + column_names: AxisColumnNames | None = None, X_layers: Sequence[str] = (), obsm_layers: Sequence[str] = (), obsp_layers: Sequence[str] = (), @@ -851,8 +853,8 @@ class JoinIDCache: owner: ExperimentAxisQuery - _cached_obs: Optional[pa.IntegerArray] = None - _cached_var: Optional[pa.IntegerArray] = None + _cached_obs: pa.IntegerArray | None = None + _cached_var: pa.IntegerArray | None = None def _is_cached(self, axis: Axis) -> bool: field = "_cached_" + axis.value diff --git a/apis/python/src/tiledbsoma/_query_condition.py b/apis/python/src/tiledbsoma/_query_condition.py index ed1c99e890..8bcd2d7429 100644 --- a/apis/python/src/tiledbsoma/_query_condition.py +++ b/apis/python/src/tiledbsoma/_query_condition.py @@ -6,8 +6,11 @@ """A high level wrapper around the Pybind11 query_condition.cc implementation for filtering query results on attribute values. """ + +from __future__ import annotations + import ast -from typing import Any, Callable, List, Optional, Tuple, Union +from typing import Any, Callable, List, Tuple, Union import attrs import numpy as np @@ -128,7 +131,7 @@ def __attrs_post_init__(self): def init_query_condition( self, schema: pa.Schema, - query_attrs: Optional[List[str]], + query_attrs: List[str] | None, ): try: qctree = QueryConditionTree(schema, query_attrs) diff --git a/apis/python/src/tiledbsoma/_read_iters.py b/apis/python/src/tiledbsoma/_read_iters.py index 92a8198ca7..11adbc7c14 100644 --- a/apis/python/src/tiledbsoma/_read_iters.py +++ b/apis/python/src/tiledbsoma/_read_iters.py @@ -5,6 +5,7 @@ """Read iterators. """ + from __future__ import annotations import abc @@ -15,7 +16,6 @@ Any, Iterator, List, - Optional, Sequence, Tuple, TypeVar, @@ -73,10 +73,10 @@ def __init__( coords: Union[ options.SparseDFCoords, options.SparseNDCoords, options.DenseNDCoords ], - column_names: Optional[Sequence[str]], + column_names: Sequence[str] | None, result_order: clib.ResultOrder, - value_filter: Optional[str], - platform_config: Optional[options.PlatformConfig], + value_filter: str | None, + platform_config: options.PlatformConfig | None, ): """Initalizes a new TableReadIter for SOMAArrays. @@ -90,7 +90,7 @@ def __init__( for each index dimension, which rows to read. ``()`` means no constraint -- all IDs. - column_names (Optional[Sequence[str]]): + column_names (Sequence[str] | None): The named columns to read and return. ``None`` means no constraint -- all column names. @@ -98,10 +98,10 @@ def __init__( Order of read results. This can be one of automatic, rowmajor, or colmajor. - value_filter (Optional[str]): + value_filter (str | None): An optional [value filter] to apply to the results. - platform_config (Optional[options.PlatformConfig]): + platform_config (options.PlatformConfig | None): Pass in parameters for tuning reads. """ @@ -131,12 +131,12 @@ def __init__( coords: options.SparseNDCoords, axis: Union[int, Sequence[int]], result_order: clib.ResultOrder, - platform_config: Optional[options.PlatformConfig], + platform_config: options.PlatformConfig | None, *, - size: Optional[Union[int, Sequence[int]]] = None, - reindex_disable_on_axis: Optional[Union[int, Sequence[int]]] = None, + size: int | Sequence[int] | None = None, + reindex_disable_on_axis: int | Sequence[int] | None = None, eager: bool = True, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ): super().__init__() @@ -192,8 +192,8 @@ def _validate_args( cls, shape: Union[NTuple, Sequence[int]], axis: Union[int, Sequence[int]], - size: Optional[Union[int, Sequence[int]]] = None, - reindex_disable_on_axis: Optional[Union[int, Sequence[int]]] = None, + size: int | Sequence[int] | None = None, + reindex_disable_on_axis: int | Sequence[int] | None = None, ) -> Tuple[List[int], List[int], List[int]]: """ Class method to validate and normalize common user-provided arguments axis, size and reindex_disable_on_axis. @@ -262,7 +262,7 @@ def concat(self) -> _RT: raise NotImplementedError("Blockwise iterators do not support concat operation") def _maybe_eager_iterator( - self, x: Iterator[_EagerRT], _pool: Optional[ThreadPoolExecutor] = None + self, x: Iterator[_EagerRT], _pool: ThreadPoolExecutor | None = None ) -> Iterator[_EagerRT]: """Private""" return EagerIterator(x, pool=_pool) if self.eager else x @@ -292,7 +292,7 @@ def _table_reader(self) -> Iterator[BlockwiseTableReadIterResult]: def _reindexed_table_reader( self, - _pool: Optional[ThreadPoolExecutor] = None, + _pool: ThreadPoolExecutor | None = None, ) -> Iterator[BlockwiseTableReadIterResult]: """Private. Blockwise table reader w/ reindexing. Helper function for sub-class use""" for tbl, coords in self._maybe_eager_iterator(self._table_reader(), _pool): @@ -335,13 +335,13 @@ def __init__( coords: options.SparseNDCoords, axis: Union[int, Sequence[int]], result_order: clib.ResultOrder, - platform_config: Optional[options.PlatformConfig], + platform_config: options.PlatformConfig | None, *, - size: Optional[Union[int, Sequence[int]]] = None, - reindex_disable_on_axis: Optional[Union[int, Sequence[int]]] = None, + size: int | Sequence[int] | None = None, + reindex_disable_on_axis: int | Sequence[int] | None = None, eager: bool = True, compress: bool = True, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ): self.compress = compress self.context = context @@ -390,7 +390,7 @@ def _create_reader(self) -> Iterator[BlockwiseScipyReadIterResult]: ) def _sorted_tbl_reader( - self, _pool: Optional[ThreadPoolExecutor] = None + self, _pool: ThreadPoolExecutor | None = None ) -> Iterator[Tuple[IJDType, IndicesType]]: """Private. Read reindexed tables and sort them. Yield as ((i,j),d)""" for coo_tbl, indices in self._maybe_eager_iterator( @@ -424,7 +424,7 @@ def _mk_shape( return cast(Tuple[int, int], tuple(_sp_shape)) def _coo_reader( - self, _pool: Optional[ThreadPoolExecutor] = None + self, _pool: ThreadPoolExecutor | None = None ) -> Iterator[Tuple[sparse.coo_matrix, IndicesType]]: """Private. Uncompressed variants""" assert not self.compress @@ -446,7 +446,7 @@ def _coo_reader( yield sp, indices def _cs_reader( - self, _pool: Optional[ThreadPoolExecutor] = None + self, _pool: ThreadPoolExecutor | None = None ) -> Iterator[Tuple[Union[sparse.csr_matrix, sparse.csc_matrix], IndicesType],]: """Private. Compressed sparse variants""" assert self.compress @@ -479,7 +479,7 @@ def __init__( coords: options.SparseDFCoords, shape: NTuple, result_order: clib.ResultOrder, - platform_config: Optional[options.PlatformConfig], + platform_config: options.PlatformConfig | None, ): self.array = array self.coords = coords @@ -549,10 +549,10 @@ def __init__( coords: Union[ options.SparseDFCoords, options.SparseNDCoords, options.DenseNDCoords ], - column_names: Optional[Sequence[str]], + column_names: Sequence[str] | None, result_order: clib.ResultOrder, - value_filter: Optional[str], - platform_config: Optional[options.PlatformConfig], + value_filter: str | None, + platform_config: options.PlatformConfig | None, ): clib_handle = array._handle._handle @@ -628,6 +628,6 @@ def _coords_strider( _ElemT = TypeVar("_ElemT") -def _pad_with_none(s: Sequence[_ElemT], to_length: int) -> Tuple[Optional[_ElemT], ...]: +def _pad_with_none(s: Sequence[_ElemT], to_length: int) -> Tuple[_ElemT | None, ...]: """Given a sequence, pad length to a user-specified length, with None values""" return tuple(s[i] if i < len(s) else None for i in range(to_length)) diff --git a/apis/python/src/tiledbsoma/_scene.py b/apis/python/src/tiledbsoma/_scene.py index 71aabdda27..d107a65fa8 100644 --- a/apis/python/src/tiledbsoma/_scene.py +++ b/apis/python/src/tiledbsoma/_scene.py @@ -6,8 +6,10 @@ Implementation of a SOMA Scene """ +from __future__ import annotations + import warnings -from typing import Any, List, Optional, Sequence, Tuple, Type, TypeVar, Union +from typing import Any, List, Sequence, Tuple, Type, TypeVar, Union import somacore from somacore import ( @@ -71,10 +73,10 @@ def create( cls, uri: str, *, - coordinate_space: Optional[Union[Sequence[str], CoordinateSpace]] = None, - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + coordinate_space: Sequence[str] | CoordinateSpace | None = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: """Creates a new scene at the given URI. @@ -135,7 +137,7 @@ def __init__( super().__init__(handle, **kwargs) coord_space = self.metadata.get(SOMA_COORDINATE_SPACE_METADATA_KEY) if coord_space is None: - self._coord_space: Optional[CoordinateSpace] = None + self._coord_space: CoordinateSpace | None = None else: self._coord_space = coordinate_space_from_json(coord_space) @@ -168,7 +170,7 @@ def _set_transform_to_element( key: str, transform: CoordinateTransform, subcollection: Union[str, Sequence[str]], - coordinate_space: Optional[CoordinateSpace], + coordinate_space: CoordinateSpace | None, ) -> _SE: # Check the transform is compatible with the coordinate spaces of the scene # and the new element coordinate space (if provided). @@ -221,7 +223,7 @@ def _set_transform_to_element( return elem @property - def coordinate_space(self) -> Optional[CoordinateSpace]: + def coordinate_space(self) -> CoordinateSpace | None: """Coordinate system for this scene. Lifecycle: @@ -246,8 +248,8 @@ def add_new_geometry_dataframe( key: str, subcollection: Union[str, Sequence[str]], *, - transform: Optional[CoordinateTransform], - uri: Optional[str] = None, + transform: CoordinateTransform | None, + uri: str | None = None, **kwargs: Any, ) -> GeometryDataFrame: """Adds a ``GeometryDataFrame`` to the scene and sets a coordinate transform @@ -290,8 +292,8 @@ def add_new_multiscale_image( key: str, subcollection: Union[str, Sequence[str]], *, - transform: Optional[CoordinateTransform], - uri: Optional[str] = None, + transform: CoordinateTransform | None, + uri: str | None = None, coordinate_space: Union[Sequence[str], CoordinateSpace] = ("x", "y"), **kwargs: Any, ) -> MultiscaleImage: @@ -381,8 +383,8 @@ def add_new_point_cloud_dataframe( key: str, subcollection: Union[str, Sequence[str]], *, - transform: Optional[CoordinateTransform], - uri: Optional[str] = None, + transform: CoordinateTransform | None, + uri: str | None = None, coordinate_space: Union[Sequence[str], CoordinateSpace] = ("x", "y"), **kwargs: Any, ) -> PointCloudDataFrame: @@ -473,7 +475,7 @@ def set_transform_to_geometry_dataframe( subcollection: Union[str, Sequence[str]] = "obsl", *, transform: CoordinateTransform, - coordinate_space: Optional[CoordinateSpace] = None, + coordinate_space: CoordinateSpace | None = None, ) -> GeometryDataFrame: """Adds the coordinate transform for the scene coordinate space to a geometry dataframe stored in the scene. @@ -508,7 +510,7 @@ def set_transform_to_multiscale_image( subcollection: Union[str, Sequence[str]] = "img", *, transform: CoordinateTransform, - coordinate_space: Optional[CoordinateSpace] = None, + coordinate_space: CoordinateSpace | None = None, ) -> MultiscaleImage: """Adds the coordinate transform for the scene coordinate space to a multiscale image stored in the scene. @@ -545,7 +547,7 @@ def set_transform_to_point_cloud_dataframe( subcollection: Union[str, Sequence[str]] = "obsl", *, transform: CoordinateTransform, - coordinate_space: Optional[CoordinateSpace] = None, + coordinate_space: CoordinateSpace | None = None, ) -> PointCloudDataFrame: """Adds the coordinate transform for the scene coordinate space to a point cloud dataframe stored in the scene. @@ -605,7 +607,7 @@ def get_transform_from_multiscale_image( key: str, subcollection: str = "img", *, - level: Optional[Union[str, int]] = None, + level: str | int | None = None, ) -> CoordinateTransform: """Returns the coordinate transformation from the requested multiscale image to the scene. @@ -701,7 +703,7 @@ def get_transform_to_multiscale_image( key: str, subcollection: str = "img", *, - level: Optional[Union[str, int]] = None, + level: str | int | None = None, ) -> CoordinateTransform: """Returns the coordinate transformation from the scene to a requested multiscale image. diff --git a/apis/python/src/tiledbsoma/_soma_group.py b/apis/python/src/tiledbsoma/_soma_group.py index c56fbba370..0526ce7544 100644 --- a/apis/python/src/tiledbsoma/_soma_group.py +++ b/apis/python/src/tiledbsoma/_soma_group.py @@ -3,6 +3,8 @@ # # Licensed under the MIT License. +from __future__ import annotations + import re from typing import ( Any, @@ -10,7 +12,6 @@ Generic, Iterable, Iterator, - Optional, Set, Type, TypeVar, @@ -44,7 +45,7 @@ class _CachedElement: """Item we have loaded in the cache of a collection.""" entry: _tdb_handles.GroupEntry - soma: Optional[AnySOMAObject] = None + soma: AnySOMAObject | None = None """The reified object, if it has been opened.""" @@ -188,7 +189,7 @@ def _add_new_element( key: str, kind: Type[_TDBO], factory: Callable[[str], _TDBO], - user_uri: Optional[str], + user_uri: str | None, ) -> _TDBO: """Handles the common parts of adding new elements. @@ -220,7 +221,7 @@ def _add_new_element( self._close_stack.enter_context(child) return child - def _new_child_uri(self, *, key: str, user_uri: Optional[str]) -> "_ChildURI": + def _new_child_uri(self, *, key: str, user_uri: str | None) -> "_ChildURI": maybe_relative_uri = user_uri or _sanitize_for_path(key) if not is_relative_uri(maybe_relative_uri): # It's an absolute URI. @@ -249,7 +250,7 @@ def set( key: str, value: CollectionElementType, *, - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, ) -> Self: """Adds an element to the collection. diff --git a/apis/python/src/tiledbsoma/_soma_object.py b/apis/python/src/tiledbsoma/_soma_object.py index 3c2776036f..ed5ba2fbe4 100644 --- a/apis/python/src/tiledbsoma/_soma_object.py +++ b/apis/python/src/tiledbsoma/_soma_object.py @@ -3,9 +3,11 @@ # # Licensed under the MIT License. +from __future__ import annotations + import datetime from contextlib import ExitStack -from typing import Any, Generic, MutableMapping, Optional, Type, TypeVar, Union +from typing import Any, Generic, MutableMapping, Type, TypeVar, Union import somacore from somacore import options @@ -59,10 +61,10 @@ def open( 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, + tiledb_timestamp: OpenTimestamp | None = None, + context: SOMATileDBContext | None = None, + platform_config: options.PlatformConfig | None = None, + clib_type: str | None = None, ) -> Self: """Opens this specific type of SOMA object. @@ -149,7 +151,7 @@ def __init__( self._close_stack.enter_context(self._handle) def reopen( - self, mode: options.OpenMode, tiledb_timestamp: Optional[OpenTimestamp] = None + self, mode: options.OpenMode, tiledb_timestamp: OpenTimestamp | None = None ) -> Self: """ Return a new copy of the SOMAObject with the given mode at the current @@ -283,8 +285,8 @@ def tiledb_timestamp_ms(self) -> int: def exists( cls, uri: str, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> bool: """ Finds whether an object of this type exists at the given URI. diff --git a/apis/python/src/tiledbsoma/_sparse_nd_array.py b/apis/python/src/tiledbsoma/_sparse_nd_array.py index 60891c9629..2e5638ba5f 100644 --- a/apis/python/src/tiledbsoma/_sparse_nd_array.py +++ b/apis/python/src/tiledbsoma/_sparse_nd_array.py @@ -6,12 +6,12 @@ """ Implementation of SOMA SparseNDArray. """ + from __future__ import annotations import itertools from typing import ( Dict, - Optional, Sequence, Tuple, Union, @@ -116,9 +116,9 @@ def create( *, type: pa.DataType, shape: Sequence[Union[int, None]], - platform_config: Optional[options.PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, - tiledb_timestamp: Optional[OpenTimestamp] = None, + platform_config: options.PlatformConfig | None = None, + context: SOMATileDBContext | None = None, + tiledb_timestamp: OpenTimestamp | None = None, ) -> Self: context = _validate_soma_tiledb_context(context) @@ -228,8 +228,8 @@ def read( *, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, - platform_config: Optional[PlatformConfig] = None, + partitions: options.ReadPartitions | None = None, + platform_config: PlatformConfig | None = None, ) -> "SparseNDArrayRead": """Reads a user-defined slice of the :class:`SparseNDArray`. @@ -285,7 +285,7 @@ def write( pa.Table, ], *, - platform_config: Optional[PlatformConfig] = None, + platform_config: PlatformConfig | None = None, ) -> Self: """ Writes an Arrow object to the SparseNDArray. @@ -412,7 +412,7 @@ def write( def _dim_capacity_and_extent( cls, dim_name: str, - dim_shape: Optional[int], + dim_shape: int | None, ndim: int, # not needed for sparse create_options: TileDBCreateOptions, ) -> Tuple[int, int]: @@ -516,7 +516,7 @@ def __init__( array: SparseNDArray, coords: options.SparseNDCoords, result_order: clib.ResultOrder, - platform_config: Optional[options.PlatformConfig], + platform_config: options.PlatformConfig | None, ): """ Lifecycle: @@ -546,7 +546,7 @@ class SparseNDArrayRead(_SparseNDArrayReadBase): Maturing. """ - def coos(self, shape: Optional[NTuple] = None) -> SparseCOOTensorReadIter: + def coos(self, shape: NTuple | None = None) -> SparseCOOTensorReadIter: """ Returns an iterator of `Arrow SparseCOOTensor `_. @@ -589,8 +589,8 @@ def blockwise( self, axis: Union[int, Sequence[int]], *, - size: Optional[Union[int, Sequence[int]]] = None, - reindex_disable_on_axis: Optional[Union[int, Sequence[int]]] = None, + size: int | Sequence[int] | None = None, + reindex_disable_on_axis: int | Sequence[int] | None = None, eager: bool = True, ) -> SparseNDArrayBlockwiseRead: """ @@ -672,10 +672,10 @@ def __init__( coords: options.SparseNDCoords, axis: Union[int, Sequence[int]], result_order: clib.ResultOrder, - platform_config: Optional[options.PlatformConfig], + platform_config: options.PlatformConfig | None, *, - size: Optional[Union[int, Sequence[int]]], - reindex_disable_on_axis: Optional[Union[int, Sequence[int]]], + size: int | Sequence[int] | None, + reindex_disable_on_axis: int | Sequence[int] | None, eager: bool = True, ): super().__init__(array, coords, result_order, platform_config) diff --git a/apis/python/src/tiledbsoma/_spatial_dataframe.py b/apis/python/src/tiledbsoma/_spatial_dataframe.py index 3c6abba155..18490ea77e 100644 --- a/apis/python/src/tiledbsoma/_spatial_dataframe.py +++ b/apis/python/src/tiledbsoma/_spatial_dataframe.py @@ -6,7 +6,10 @@ """ Implementation of a base class shared between GeometryDataFrame and PointCloudDataFrame """ -from typing import Any, Optional, Sequence, Tuple, Union + +from __future__ import annotations + +from typing import Any, Sequence, Tuple, Union import pyarrow as pa import somacore @@ -68,13 +71,13 @@ def domain(self) -> Tuple[Tuple[Any, Any], ...]: def read( self, coords: options.SparseDFCoords = (), - column_names: Optional[Sequence[str]] = None, + column_names: Sequence[str] | None = None, *, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, + value_filter: str | None = None, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, - platform_config: Optional[options.PlatformConfig] = None, + partitions: options.ReadPartitions | None = None, + platform_config: options.PlatformConfig | None = None, ) -> TableReadIter: """Reads a user-defined slice of data into Arrow tables. @@ -101,16 +104,16 @@ def read( def read_spatial_region( self, - region: Optional[options.SpatialRegion] = None, - column_names: Optional[Sequence[str]] = None, + region: options.SpatialRegion | None = None, + column_names: Sequence[str] | None = None, *, - region_transform: Optional[CoordinateTransform] = None, - region_coord_space: Optional[CoordinateSpace] = None, + region_transform: CoordinateTransform | None = None, + region_coord_space: CoordinateSpace | None = None, batch_size: options.BatchSize = _UNBATCHED, - partitions: Optional[options.ReadPartitions] = None, + partitions: options.ReadPartitions | None = None, result_order: options.ResultOrderStr = options.ResultOrder.AUTO, - value_filter: Optional[str] = None, - platform_config: Optional[options.PlatformConfig] = None, + value_filter: str | None = None, + platform_config: options.PlatformConfig | None = None, ) -> somacore.SpatialRead[somacore.ReadIter[pa.Table]]: """Reads data intersecting an user-defined region of space into a :class:`SpatialRead` with data in Arrow tables. @@ -151,7 +154,7 @@ def write( self, values: Union[pa.RecordBatch, pa.Table], *, - platform_config: Optional[options.PlatformConfig] = None, + platform_config: options.PlatformConfig | None = None, ) -> Self: """Writes the data from an Arrow table to the persistent object. diff --git a/apis/python/src/tiledbsoma/_spatial_util.py b/apis/python/src/tiledbsoma/_spatial_util.py index a16a364383..9d5ce7bb59 100644 --- a/apis/python/src/tiledbsoma/_spatial_util.py +++ b/apis/python/src/tiledbsoma/_spatial_util.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import json -from typing import Any, Dict, Optional, Tuple, Type +from typing import Any, Dict, Tuple, Type import numpy as np import pyarrow as pa @@ -113,17 +115,17 @@ def transform_region( def process_image_region( - region: Optional[options.SpatialRegion], + region: options.SpatialRegion | None, transform: somacore.CoordinateTransform, channel_coords: options.DenseCoord, data_order: Tuple[int, ...], ) -> Tuple[ - options.DenseNDCoords, Optional[options.SpatialRegion], somacore.CoordinateTransform + options.DenseNDCoords, options.SpatialRegion | None, somacore.CoordinateTransform ]: if region is None: # Select the full region. - data_region: Optional[options.SpatialRegion] = None + data_region: options.SpatialRegion | None = None x_coords: options.DenseCoord = None y_coords: options.DenseCoord = None else: @@ -173,7 +175,7 @@ def process_image_region( def process_spatial_df_region( - region: Optional[options.SpatialRegion], + region: options.SpatialRegion | None, transform: somacore.CoordinateTransform, coords_by_name: Dict[str, options.SparseDFCoord], index_columns: Tuple[str, ...], @@ -181,7 +183,7 @@ def process_spatial_df_region( schema: pa.Schema, ) -> Tuple[ options.SparseDFCoords, - Optional[options.SpatialRegion], + options.SpatialRegion | None, somacore.CoordinateTransform, ]: # Check provided coords are valid. @@ -194,7 +196,7 @@ def process_spatial_df_region( # to the coords_by_name map. if region is None: # Leave spatial coords as None - this will select the entire region. - data_region: Optional[options.SpatialRegion] = None + data_region: options.SpatialRegion | None = None else: # Restricted to guarantee data region is a box. if isinstance(region, shapely.GeometryType): diff --git a/apis/python/src/tiledbsoma/_tdb_handles.py b/apis/python/src/tiledbsoma/_tdb_handles.py index 54d802d5dd..5087a2daf8 100644 --- a/apis/python/src/tiledbsoma/_tdb_handles.py +++ b/apis/python/src/tiledbsoma/_tdb_handles.py @@ -8,6 +8,8 @@ ``open``, ``ArrayWrapper.open``, ``GroupWrapper.open`` are the important parts. """ +from __future__ import annotations + import abc import enum from typing import ( @@ -18,7 +20,6 @@ List, Mapping, MutableMapping, - Optional, Sequence, Tuple, Type, @@ -64,8 +65,8 @@ def open( uri: str, mode: options.OpenMode, context: SOMATileDBContext, - timestamp: Optional[OpenTimestamp], - clib_type: Optional[str] = None, + timestamp: OpenTimestamp | None, + clib_type: str | None = None, ) -> "Wrapper[RawHandle]": """Determine whether the URI is an array or group, and open it.""" open_mode = clib.OpenMode.read if mode == "r" else clib.OpenMode.write @@ -140,7 +141,7 @@ class Wrapper(Generic[_RawHdl_co], metaclass=abc.ABCMeta): timestamp_ms: int _handle: _RawHdl_co closed: bool = attrs.field(default=False, init=False) - clib_type: Optional[str] = None + clib_type: str | None = None @classmethod def open( @@ -148,7 +149,7 @@ def open( uri: str, mode: options.OpenMode, context: SOMATileDBContext, - timestamp: Optional[OpenTimestamp], + timestamp: OpenTimestamp | None, ) -> Self: if mode not in ("r", "w"): raise ValueError(f"Invalid open mode {mode!r}") @@ -202,7 +203,7 @@ def _opener( raise NotImplementedError() def reopen( - self, mode: options.OpenMode, timestamp: Optional[OpenTimestamp] + self, mode: options.OpenMode, timestamp: OpenTimestamp | None ) -> clib.SOMAObject: if mode not in ("r", "w"): raise ValueError( @@ -449,12 +450,12 @@ def maxshape(self) -> Tuple[int, ...]: return cast(Tuple[int, ...], tuple(self._handle.maxshape)) @property - def maybe_soma_joinid_shape(self) -> Optional[int]: + def maybe_soma_joinid_shape(self) -> int | None: """Only implemented for DataFrame.""" raise NotImplementedError @property - def maybe_soma_joinid_maxshape(self) -> Optional[int]: + def maybe_soma_joinid_maxshape(self) -> int | None: """Only implemented for DataFrame.""" raise NotImplementedError @@ -548,14 +549,14 @@ def write(self, values: pa.RecordBatch) -> None: self._handle.write(values) @property - def maybe_soma_joinid_shape(self) -> Optional[int]: + def maybe_soma_joinid_shape(self) -> int | None: """Wrapper-class internals""" - return cast(Optional[int], self._handle.maybe_soma_joinid_shape) + return cast(Union[int, None], self._handle.maybe_soma_joinid_shape) @property - def maybe_soma_joinid_maxshape(self) -> Optional[int]: + def maybe_soma_joinid_maxshape(self) -> int | None: """Wrapper-class internals""" - return cast(Optional[int], self._handle.maybe_soma_joinid_maxshape) + return cast(Union[int, None], self._handle.maybe_soma_joinid_maxshape) @property def tiledbsoma_has_upgraded_domain(self) -> bool: diff --git a/apis/python/src/tiledbsoma/_util.py b/apis/python/src/tiledbsoma/_util.py index e007ab9c16..9bce8a7e8b 100644 --- a/apis/python/src/tiledbsoma/_util.py +++ b/apis/python/src/tiledbsoma/_util.py @@ -3,6 +3,8 @@ # # Licensed under the MIT License. +from __future__ import annotations + import datetime import json import pathlib @@ -15,7 +17,6 @@ Dict, List, Mapping, - Optional, Sequence, Tuple, Type, @@ -149,7 +150,7 @@ class NonNumericDimensionError(TypeError): def slice_to_numeric_range( slc: Slice[Any], domain: Tuple[_T, _T] -) -> Optional[Tuple[_T, _T]]: +) -> Tuple[_T, _T] | None: """Constrains the given slice to the ``domain`` for numeric dimensions. We assume the slice has already been validated by validate_slice. @@ -244,7 +245,7 @@ def check_type( ) -def check_unpartitioned(partitions: Optional[options.ReadPartitions]) -> None: +def check_unpartitioned(partitions: options.ReadPartitions | None) -> None: """Ensures that we're not being asked for a partitioned read. Because we currently don't support partitioned reads, we should reject all @@ -325,7 +326,7 @@ def cast_values_to_target_schema(values: pa.Table, schema: pa.Schema) -> pa.Tabl def build_clib_platform_config( - platform_config: Optional[options.PlatformConfig], + platform_config: options.PlatformConfig | None, ) -> clib.PlatformConfig: """ Copy over Python PlatformConfig values to the C++ clib.PlatformConfig @@ -352,7 +353,7 @@ def build_clib_platform_config( return plt_cfg -def _build_column_config(col: Optional[Mapping[str, _ColumnConfig]]) -> str: +def _build_column_config(col: Mapping[str, _ColumnConfig] | None) -> str: column_config: Dict[str, Dict[str, Union[_JSONFilterList, int]]] = dict() if col is None: @@ -370,7 +371,7 @@ def _build_column_config(col: Optional[Mapping[str, _ColumnConfig]]) -> str: def _build_filter_list( - filters: Optional[Tuple[_DictFilterSpec, ...]], return_json: bool = True + filters: Tuple[_DictFilterSpec, ...] | None, return_json: bool = True ) -> _JSONFilterList: _convert_filter = { "GzipFilter": "GZIP", diff --git a/apis/python/src/tiledbsoma/io/_common.py b/apis/python/src/tiledbsoma/io/_common.py index 803f61d52b..b96d58cec6 100644 --- a/apis/python/src/tiledbsoma/io/_common.py +++ b/apis/python/src/tiledbsoma/io/_common.py @@ -4,7 +4,10 @@ # Licensed under the MIT License. """Common constants and types used during ingestion/outgestion.""" -from typing import Dict, List, Mapping, Optional, Union + +from __future__ import annotations + +from typing import Dict, List, Mapping, Union import h5py import numpy as np @@ -35,7 +38,7 @@ UnsDictNode = Union[UnsLeaf, Dict[str, "UnsDictNode"]] UnsDict = Dict[str, UnsDictNode] -AdditionalMetadata = Optional[Dict[str, Metadatum]] +AdditionalMetadata = Union[Dict[str, Metadatum], None] # Arrays of strings from AnnData's uns are stored in SOMA as SOMADataFrame, # since SOMA ND arrays are necessarily arrays *of numbers*. This is okay since diff --git a/apis/python/src/tiledbsoma/io/_registration/ambient_label_mappings.py b/apis/python/src/tiledbsoma/io/_registration/ambient_label_mappings.py index 50a77becd5..a08f3ea784 100644 --- a/apis/python/src/tiledbsoma/io/_registration/ambient_label_mappings.py +++ b/apis/python/src/tiledbsoma/io/_registration/ambient_label_mappings.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import json -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Sequence import anndata as ad import attrs @@ -61,7 +63,7 @@ def from_isolated_dataframe( cls, df: pd.DataFrame, *, - index_field_name: Optional[str] = None, + index_field_name: str | None = None, ) -> Self: """Factory method to compute an axis label-to-SOMA-join-ID mapping for a single dataframe in isolation. This is used when a user is ingesting a single AnnData/H5AD to a single SOMA @@ -139,8 +141,8 @@ def from_isolated_anndata( adata: ad.AnnData, *, measurement_name: str, - obs_field_name: Optional[str] = None, - var_field_name: Optional[str] = None, + obs_field_name: str | None = None, + var_field_name: str | None = None, ) -> Self: """Factory method to compute the label-to-SOMA-join-ID mappings for a single input file in isolation. This is used when a user is ingesting a single AnnData/H5AD to a single SOMA @@ -186,9 +188,9 @@ def from_isolated_h5ad( h5ad_file_name: str, *, measurement_name: str, - obs_field_name: Optional[str] = None, - var_field_name: Optional[str] = None, - context: Optional[SOMATileDBContext] = None, + obs_field_name: str | None = None, + var_field_name: str | None = None, + context: SOMATileDBContext | None = None, ) -> Self: """Factory method to compute label-to-SOMA-join-ID mappings for a single input file in isolation. This is used when a user is ingesting a single AnnData/H5AD to a single SOMA @@ -206,11 +208,11 @@ def from_isolated_h5ad( @classmethod def from_isolated_soma_experiment( cls, - experiment_uri: Optional[str] = None, + experiment_uri: str | None = None, *, obs_field_name: str = "obs_id", var_field_name: str = "var_id", - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> Self: """Factory method to compute label-to-SOMA-join-ID mappings for a single SOMA experiment in isolation. These are already committed to SOMA storage, so they are the unchangeable inputs @@ -348,12 +350,12 @@ def from_anndata_append_on_experiment( @classmethod def _acquire_experiment_mappings( cls, - experiment_uri: Optional[str], + experiment_uri: str | None, *, measurement_name: str, obs_field_name: str, var_field_name: str, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> Self: """Acquires label-to-ID mappings from the baseline, already-written SOMA experiment.""" @@ -388,14 +390,14 @@ def _acquire_experiment_mappings( @classmethod def from_anndata_appends_on_experiment( cls, - experiment_uri: Optional[str], + experiment_uri: str | None, adatas: Sequence[ad.AnnData], *, measurement_name: str, obs_field_name: str, var_field_name: str, append_obsm_varm: bool = False, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> Self: """Extends registration data from the baseline, already-written SOMA experiment to include multiple H5AD input files. If ``experiment_uri`` @@ -434,7 +436,7 @@ def from_h5ad_append_on_experiment( obs_field_name: str = "obs_id", var_field_name: str = "var_id", append_obsm_varm: bool = False, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> Self: """Extends registration data to one more H5AD input file.""" tiledbsoma.logging.logger.info(f"Registration: registering {h5ad_file_name}.") @@ -452,14 +454,14 @@ def from_h5ad_append_on_experiment( @classmethod def from_h5ad_appends_on_experiment( cls, - experiment_uri: Optional[str], + experiment_uri: str | None, h5ad_file_names: Sequence[str], *, measurement_name: str, obs_field_name: str, var_field_name: str, append_obsm_varm: bool = False, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> Self: """Extends registration data from the baseline, already-written SOMA experiment to include multiple H5AD input files.""" diff --git a/apis/python/src/tiledbsoma/io/_registration/signatures.py b/apis/python/src/tiledbsoma/io/_registration/signatures.py index 9caf394d11..73838ddeee 100644 --- a/apis/python/src/tiledbsoma/io/_registration/signatures.py +++ b/apis/python/src/tiledbsoma/io/_registration/signatures.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import json -from typing import Dict, Optional, Union +from typing import Dict, Union import anndata as ad import attrs @@ -82,7 +84,7 @@ def _string_dict_from_pandas_dataframe( def _prepare_df_for_ingest( - df: pd.DataFrame, id_column_name: Optional[str] + df: pd.DataFrame, id_column_name: str | None ) -> OriginalIndexMetadata: """Prepare a `pd.DataFrame` for persisting as a SOMA DataFrame: demote its index to a column (to make way for a required `soma_joinid` index), and compute and return metadata for restoring the index column and name later (on @@ -179,12 +181,12 @@ class Signature: # Note: string/string dicts are easier to serialize/deserialize than pa.Schema obs_schema: Dict[str, str] var_schema: Dict[str, str] - raw_var_schema: Optional[Dict[str, str]] + raw_var_schema: Dict[str, str] | None # TODO include 'raw' in X_dtypes or no? Different for AnnData and for SOMA. When in doubt, # lean SOMA. X_dtypes: Dict[str, str] - raw_X_dtype: Optional[str] + raw_X_dtype: str | None obsm_dtypes: Dict[str, str] varm_dtypes: Dict[str, str] @@ -279,7 +281,7 @@ def from_soma_experiment( cls, uri: str, measurement_name: str = "RNA", - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> Self: """ Constructs a pre-check signature from a SOMA experiment, which can be compared against diff --git a/apis/python/src/tiledbsoma/io/_util.py b/apis/python/src/tiledbsoma/io/_util.py index e69c97a673..2c1ebebd67 100644 --- a/apis/python/src/tiledbsoma/io/_util.py +++ b/apis/python/src/tiledbsoma/io/_util.py @@ -2,12 +2,14 @@ # Copyright (c) 2021-2024 TileDB, Inc. # # Licensed under the MIT License. + +from __future__ import annotations + import pathlib from contextlib import contextmanager from typing import ( ContextManager, Iterator, - Optional, Union, ) from unittest import mock @@ -42,7 +44,7 @@ @contextmanager def read_h5ad( - input_path: Path, *, mode: str = "r", ctx: Optional[SOMATileDBContext] = None + input_path: Path, *, mode: str = "r", ctx: SOMATileDBContext | None = None ) -> Iterator[ad.AnnData]: """ This lets us ingest H5AD with "r" (backed mode) from S3 URIs. diff --git a/apis/python/src/tiledbsoma/io/ingest.py b/apis/python/src/tiledbsoma/io/ingest.py index 1232a70ff6..183a8d5236 100644 --- a/apis/python/src/tiledbsoma/io/ingest.py +++ b/apis/python/src/tiledbsoma/io/ingest.py @@ -9,6 +9,8 @@ other formats. Currently only ``.h5ad`` (`AnnData `_) is supported. """ +from __future__ import annotations + import json import math import time @@ -18,7 +20,6 @@ List, Literal, Mapping, - Optional, Sequence, Tuple, Type, @@ -135,7 +136,7 @@ class IngestionParams: def __init__( self, ingest_mode: _IngestMode, - label_mapping: Optional[ExperimentAmbientLabelMapping], + label_mapping: ExperimentAmbientLabelMapping | None, ) -> None: if ingest_mode == "schema_only": self.write_schema_no_data = True @@ -186,14 +187,14 @@ def __init__( # The tiledbsoma.io._registration package is private. These are the two sole user-facing API # entrypoints for append-mode soma_joinid registration. def register_h5ads( - experiment_uri: Optional[str], + experiment_uri: str | None, h5ad_file_names: Sequence[str], *, measurement_name: str, obs_field_name: str, var_field_name: str, append_obsm_varm: bool = False, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> ExperimentAmbientLabelMapping: """Extends registration data from the baseline, already-written SOMA experiment to include multiple H5AD input files. See ``from_h5ad`` and @@ -210,14 +211,14 @@ def register_h5ads( def register_anndatas( - experiment_uri: Optional[str], + experiment_uri: str | None, adatas: Sequence[ad.AnnData], *, measurement_name: str, obs_field_name: str, var_field_name: str, append_obsm_varm: bool = False, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> ExperimentAmbientLabelMapping: """Extends registration data from the baseline, already-written SOMA experiment to include multiple H5AD input files. See ``from_h5ad`` and @@ -238,17 +239,17 @@ def from_h5ad( input_path: Path, measurement_name: str, *, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, obs_id_name: str = "obs_id", var_id_name: str = "var_id", X_layer_name: str = "data", raw_X_layer_name: str = "data", ingest_mode: IngestMode = "write", - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, X_kind: Union[Type[SparseNDArray], Type[DenseNDArray]] = SparseNDArray, - registration_mapping: Optional[ExperimentAmbientLabelMapping] = None, - uns_keys: Optional[Sequence[str]] = None, + registration_mapping: ExperimentAmbientLabelMapping | None = None, + uns_keys: Sequence[str] | None = None, additional_metadata: AdditionalMetadata = None, ) -> str: """Reads an ``.h5ad`` file and writes it to an :class:`Experiment`. @@ -396,7 +397,7 @@ def from_h5ad( class IngestCtx(TypedDict): """Convenience type-alias for kwargs passed to ingest functions.""" - context: Optional[SOMATileDBContext] + context: SOMATileDBContext | None ingestion_params: IngestionParams additional_metadata: AdditionalMetadata @@ -406,7 +407,7 @@ class IngestPlatformCtx(IngestCtx): Extends :class:`IngestCtx`, adds ``platform_config``.""" - platform_config: Optional[PlatformConfig] + platform_config: PlatformConfig | None # ---------------------------------------------------------------- @@ -415,17 +416,17 @@ def from_anndata( anndata: ad.AnnData, measurement_name: str, *, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, obs_id_name: str = "obs_id", var_id_name: str = "var_id", X_layer_name: str = "data", raw_X_layer_name: str = "data", ingest_mode: IngestMode = "write", - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, X_kind: Union[Type[SparseNDArray], Type[DenseNDArray]] = SparseNDArray, - registration_mapping: Optional[ExperimentAmbientLabelMapping] = None, - uns_keys: Optional[Sequence[str]] = None, + registration_mapping: ExperimentAmbientLabelMapping | None = None, + uns_keys: Sequence[str] | None = None, additional_metadata: AdditionalMetadata = None, ) -> str: """Writes an `AnnData `_ object to an :class:`Experiment`. @@ -630,7 +631,7 @@ def from_anndata( def _ingest_obs_var_m_p( ad_key: Literal["obsm", "varm", "obsp", "varp"], axis_0_mapping: AxisIDMapping, - axis_1_mapping: Optional[AxisIDMapping] = None, + axis_1_mapping: AxisIDMapping | None = None, ) -> None: ad_val = getattr(anndata, ad_key) if len(ad_val.keys()) > 0: # do not create an empty collection @@ -758,8 +759,8 @@ def append_obs( *, obs_id_name: str = "obs_id", registration_mapping: ExperimentAmbientLabelMapping, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, ) -> str: """ Writes new rows to an existing ``obs`` dataframe. (This is distinct from ``update_obs`` @@ -816,8 +817,8 @@ def append_var( *, var_id_name: str = "var_id", registration_mapping: ExperimentAmbientLabelMapping, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, ) -> str: """ Writes new rows to an existing ``var`` dataframe. (This is distinct from ``update_var`` @@ -884,8 +885,8 @@ def append_X( *, registration_mapping: ExperimentAmbientLabelMapping, X_kind: Union[Type[SparseNDArray], Type[DenseNDArray]] = SparseNDArray, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, ) -> str: """ Appends new data to an existing ``X`` matrix. Nominally to be used in conjunction @@ -960,7 +961,7 @@ def _maybe_set( key: str, value: AnySOMAObject, *, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, ) -> None: coll.verify_open_for_writing() try: @@ -976,7 +977,7 @@ def _create_or_open_collection( uri: str, *, ingestion_params: IngestionParams, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, additional_metadata: AdditionalMetadata = None, ) -> Experiment: ... @@ -987,7 +988,7 @@ def _create_or_open_collection( uri: str, *, ingestion_params: IngestionParams, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, additional_metadata: AdditionalMetadata = None, ) -> Measurement: ... @@ -998,7 +999,7 @@ def _create_or_open_collection( uri: str, *, ingestion_params: IngestionParams, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, additional_metadata: AdditionalMetadata = None, ) -> Collection[_TDBO]: ... @@ -1009,7 +1010,7 @@ def _create_or_open_collection( uri: str, *, ingestion_params: IngestionParams, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, additional_metadata: AdditionalMetadata = None, ) -> CollectionBase[_TDBO]: try: @@ -1031,7 +1032,7 @@ def _create_or_open_coll( uri: str, *, ingest_mode: IngestMode, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, ) -> Experiment: ... @@ -1041,7 +1042,7 @@ def _create_or_open_coll( uri: str, *, ingest_mode: IngestMode, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, ) -> Measurement: ... @@ -1051,7 +1052,7 @@ def _create_or_open_coll( uri: str, *, ingest_mode: IngestMode, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, ) -> Collection[_TDBO]: ... @@ -1060,7 +1061,7 @@ def _create_or_open_coll( uri: str, *, ingest_mode: IngestMode, - context: Optional[SOMATileDBContext], + context: SOMATileDBContext | None, ) -> Any: return _create_or_open_collection( cls, @@ -1192,7 +1193,7 @@ def is_cat(field: pa.Field) -> bool: def _extract_new_values_for_append( df_uri: str, arrow_table: pa.Table, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> pa.Table: """ For append mode: mostly we just go ahead and write the data, except var. @@ -1259,12 +1260,12 @@ def _write_arrow_table( def _write_dataframe( df_uri: str, df: pd.DataFrame, - id_column_name: Optional[str], + id_column_name: str | None, *, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, - platform_config: Optional[PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, + platform_config: PlatformConfig | None = None, + context: SOMATileDBContext | None = None, axis_mapping: AxisIDMapping, ) -> DataFrame: """ @@ -1300,14 +1301,14 @@ def _write_dataframe( def _write_dataframe_impl( df: pd.DataFrame, df_uri: str, - id_column_name: Optional[str], + id_column_name: str | None, *, shape: int, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, original_index_metadata: OriginalIndexMetadata = None, - platform_config: Optional[PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, + platform_config: PlatformConfig | None = None, + context: SOMATileDBContext | None = None, ) -> DataFrame: """Save a Pandas DataFrame as a SOMA DataFrame. @@ -1389,9 +1390,9 @@ def create_from_matrix( cls: Type[_NDArr], uri: str, matrix: Union[Matrix, h5py.Dataset], - platform_config: Optional[PlatformConfig] = None, + platform_config: PlatformConfig | None = None, ingest_mode: IngestMode = "write", - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ) -> _NDArr: """ Create and populate the ``soma_matrix`` from the contents of ``matrix``. @@ -1418,8 +1419,8 @@ def _create_from_matrix( *, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, - platform_config: Optional[PlatformConfig] = None, - context: Optional[SOMATileDBContext] = None, + platform_config: PlatformConfig | None = None, + context: SOMATileDBContext | None = None, axis_0_mapping: AxisIDMapping, axis_1_mapping: AxisIDMapping, ) -> _NDArr: @@ -1512,8 +1513,8 @@ def update_obs( exp: Experiment, new_data: pd.DataFrame, *, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, default_index_name: str = "obs_id", ) -> None: """ @@ -1561,8 +1562,8 @@ def update_var( new_data: pd.DataFrame, measurement_name: str, *, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, default_index_name: str = "var_id", ) -> None: """ @@ -1618,8 +1619,8 @@ def _update_dataframe( new_data: pd.DataFrame, caller_name: str, *, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig], + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None, default_index_name: str, ) -> None: """ @@ -1720,8 +1721,8 @@ def update_matrix( soma_ndarray: Union[SparseNDArray, DenseNDArray], new_data: Union[Matrix, h5py.Dataset], *, - context: Optional[SOMATileDBContext] = None, - platform_config: Optional[PlatformConfig] = None, + context: SOMATileDBContext | None = None, + platform_config: PlatformConfig | None = None, ) -> None: """ Given a ``SparseNDArray`` or ``DenseNDArray`` already opened for write, @@ -1819,7 +1820,7 @@ def add_X_layer( # E.g. a scipy.csr_matrix from scanpy analysis: X_layer_data: Union[Matrix, h5py.Dataset], ingest_mode: IngestMode = "write", - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, ) -> None: """This is useful for adding X data, for example from `Scanpy `_'s ``scanpy.pp.normalize_total``, @@ -1850,8 +1851,8 @@ def add_matrix_to_collection( # E.g. a scipy.csr_matrix from scanpy analysis: matrix_data: Union[Matrix, h5py.Dataset], ingest_mode: IngestMode = "write", - use_relative_uri: Optional[bool] = None, - context: Optional[SOMATileDBContext] = None, + use_relative_uri: bool | None = None, + context: SOMATileDBContext | None = None, ) -> None: """This is useful for adding X/obsp/varm/etc data, for example from Scanpy's ``scanpy.pp.normalize_total``, ``scanpy.pp.log1p``, etc. @@ -2541,7 +2542,7 @@ def _coo_to_table( def _chunk_is_contained_in( chunk_bounds: Sequence[Tuple[int, int]], - storage_nonempty_domain: Sequence[Tuple[Optional[int], Optional[int]]], + storage_nonempty_domain: Sequence[Tuple[int | None, int | None]], ) -> bool: """ Determines if a dim range is included within the array's non-empty domain. Ranges are inclusive @@ -2574,7 +2575,7 @@ def _chunk_is_contained_in( def _chunk_is_contained_in_axis( chunk_bounds: Sequence[Tuple[int, int]], - storage_nonempty_domain: Sequence[Tuple[Optional[int], Optional[int]]], + storage_nonempty_domain: Sequence[Tuple[int | None, int | None]], stride_axis: int, ) -> bool: """Helper function for ``_chunk_is_contained_in``.""" @@ -2599,11 +2600,11 @@ def _maybe_ingest_uns( m: Measurement, uns: UnsMapping, *, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, ingestion_params: IngestionParams, - use_relative_uri: Optional[bool], - uns_keys: Optional[Sequence[str]] = None, + use_relative_uri: bool | None, + uns_keys: Sequence[str] | None = None, additional_metadata: AdditionalMetadata = None, ) -> None: # Don't try to ingest an empty uns. @@ -2627,11 +2628,11 @@ def _ingest_uns_dict( parent_key: str, dct: UnsMapping, *, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, ingestion_params: IngestionParams, - use_relative_uri: Optional[bool], - uns_keys: Optional[Sequence[str]] = None, + use_relative_uri: bool | None, + uns_keys: Sequence[str] | None = None, level: int = 0, additional_metadata: AdditionalMetadata = None, ) -> None: @@ -2668,11 +2669,11 @@ def _ingest_uns_node( key: str, value: UnsNode, *, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, level: int, ) -> None: if isinstance(value, np.generic): @@ -2738,7 +2739,7 @@ def _ingest_uns_array( coll: AnyTileDBCollection, key: str, value: NPNDArray, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, ingest_platform_ctx: IngestPlatformCtx, ) -> None: """Ingest an uns Numpy array. @@ -2776,10 +2777,10 @@ def _ingest_uns_string_array( coll: AnyTileDBCollection, key: str, value: NPNDArray, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, *, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, ) -> None: @@ -2820,10 +2821,10 @@ def _ingest_uns_1d_string_array( coll: AnyTileDBCollection, key: str, value: NPNDArray, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, *, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, ) -> None: @@ -2865,10 +2866,10 @@ def _ingest_uns_2d_string_array( coll: AnyTileDBCollection, key: str, value: NPNDArray, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, *, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, ) -> None: @@ -2911,10 +2912,10 @@ def _ingest_uns_ndarray( coll: AnyTileDBCollection, key: str, value: NPNDArray, - platform_config: Optional[PlatformConfig], - context: Optional[SOMATileDBContext], + platform_config: PlatformConfig | None, + context: SOMATileDBContext | None, *, - use_relative_uri: Optional[bool], + use_relative_uri: bool | None, ingestion_params: IngestionParams, additional_metadata: AdditionalMetadata = None, ) -> None: diff --git a/apis/python/src/tiledbsoma/io/outgest.py b/apis/python/src/tiledbsoma/io/outgest.py index 5397ccbd35..756b11d7df 100644 --- a/apis/python/src/tiledbsoma/io/outgest.py +++ b/apis/python/src/tiledbsoma/io/outgest.py @@ -8,6 +8,7 @@ This module contains methods to export SOMA artifacts to other formats. Currently only ``.h5ad`` (`AnnData `_) is supported. """ + from __future__ import annotations import json @@ -16,7 +17,6 @@ Any, Dict, KeysView, - Optional, Sequence, Union, cast, @@ -65,11 +65,11 @@ def to_h5ad( h5ad_path: Path, measurement_name: str, *, - X_layer_name: Optional[str] = "data", - obs_id_name: Optional[str] = None, - var_id_name: Optional[str] = None, - obsm_varm_width_hints: Optional[Dict[str, Dict[str, int]]] = None, - uns_keys: Optional[Sequence[str]] = None, + X_layer_name: str | None = "data", + obs_id_name: str | None = None, + var_id_name: str | None = None, + obsm_varm_width_hints: Dict[str, Dict[str, int]] | None = None, + uns_keys: Sequence[str] | None = None, ) -> None: """Converts the experiment group to AnnData format and writes it to the specified ``.h5ad`` file. @@ -168,8 +168,8 @@ def _read_X_partitions() -> Matrix: def _read_dataframe( df: DataFrame, - default_index_name: Optional[str] = None, - fallback_index_name: Optional[str] = None, + default_index_name: str | None = None, + fallback_index_name: str | None = None, ) -> pd.DataFrame: """Outgest a SOMA DataFrame to Pandas, including restoring the original index{,.name}. @@ -231,12 +231,12 @@ def to_anndata( experiment: Experiment, measurement_name: str, *, - X_layer_name: Optional[str] = "data", - extra_X_layer_names: Optional[Union[Sequence[str], KeysView[str]]] = None, - obs_id_name: Optional[str] = None, - var_id_name: Optional[str] = None, - obsm_varm_width_hints: Optional[Dict[str, Dict[str, int]]] = None, - uns_keys: Optional[Sequence[str]] = None, + X_layer_name: str | None = "data", + extra_X_layer_names: Sequence[str] | KeysView[str] | None = None, + obs_id_name: str | None = None, + var_id_name: str | None = None, + obsm_varm_width_hints: Dict[str, Dict[str, int]] | None = None, + uns_keys: Sequence[str] | None = None, ) -> ad.AnnData: """Converts the experiment group to AnnData format. @@ -518,7 +518,7 @@ def _extract_obsm_or_varm( def _extract_uns( collection: Collection[Any], - uns_keys: Optional[Sequence[str]] = None, + uns_keys: Sequence[str] | None = None, level: int = 0, ) -> Dict[str, FutureUnsDictNode]: """ diff --git a/apis/python/src/tiledbsoma/io/shaping.py b/apis/python/src/tiledbsoma/io/shaping.py index 657d107757..2fde106bd3 100644 --- a/apis/python/src/tiledbsoma/io/shaping.py +++ b/apis/python/src/tiledbsoma/io/shaping.py @@ -9,9 +9,11 @@ Experiment. Please also see https://github.com/single-cell-data/TileDB-SOMA/issues/2407. """ +from __future__ import annotations + import io import sys -from typing import Any, Dict, Optional, Tuple, TypedDict, Union, cast +from typing import Any, Dict, Tuple, TypedDict, Union, cast import tiledbsoma @@ -23,13 +25,13 @@ class SizingArgs(TypedDict): upgrade/resize functions. """ - nobs: Optional[int] - nvars: Optional[Dict[str, int]] - ms_name: Optional[str] - coll_name: Optional[str] + nobs: int | None + nvars: Dict[str, int] | None + ms_name: str | None + coll_name: str | None verbose: bool check_only: bool - context: Optional[tiledbsoma.SOMATileDBContext] + context: tiledbsoma.SOMATileDBContext | None output_handle: Printable @@ -48,7 +50,7 @@ def _find_old_sparse_ndarray_bounds( def show_experiment_shapes( uri: str, *, - context: Optional[tiledbsoma.SOMATileDBContext] = None, + context: tiledbsoma.SOMATileDBContext | None = None, output_handle: Printable = cast(Printable, sys.stdout), ) -> bool: """For each dataframe/array contained within the SOMA ``Experiment`` pointed @@ -105,7 +107,7 @@ def upgrade_experiment_shapes( *, verbose: bool = False, check_only: bool = False, - context: Optional[tiledbsoma.SOMATileDBContext] = None, + context: tiledbsoma.SOMATileDBContext | None = None, output_handle: Printable = cast(Printable, sys.stdout), ) -> bool: """For each dataframe contained within the SOMA ``Experiment`` pointed to by @@ -168,7 +170,7 @@ def resize_experiment( nvars: Dict[str, int], verbose: bool = False, check_only: bool = False, - context: Optional[tiledbsoma.SOMATileDBContext] = None, + context: tiledbsoma.SOMATileDBContext | None = None, output_handle: Printable = cast(Printable, sys.stdout), ) -> bool: """For each dataframe contained within the SOMA ``Experiment`` pointed to by @@ -260,7 +262,7 @@ def resize_experiment( def _treewalk( uri: str, *, - node_name: Optional[str] = None, + node_name: str | None = None, visitor: Any, args: SizingArgs, ) -> bool: diff --git a/apis/python/src/tiledbsoma/io/spatial/_xarray_backend.py b/apis/python/src/tiledbsoma/io/spatial/_xarray_backend.py index 99e52ad752..75d8a9fb59 100644 --- a/apis/python/src/tiledbsoma/io/spatial/_xarray_backend.py +++ b/apis/python/src/tiledbsoma/io/spatial/_xarray_backend.py @@ -2,9 +2,12 @@ # Copyright (c) 2024 TileDB, Inc # # Licensed under the MIT License. + +from __future__ import annotations + import json import warnings -from typing import Any, Mapping, Optional, Sequence, Tuple, Union +from typing import Any, Mapping, Sequence, Tuple, Union import dask.array as da import numpy as np @@ -41,7 +44,7 @@ def __init__( self, uri: str, *, - context: Optional[SOMATileDBContext] = None, + context: SOMATileDBContext | None = None, ): self._array = DenseNDArray.open(uri, context=context) self._dtype: np.typing.DTypeLike = self._array.schema.field( @@ -123,9 +126,9 @@ def dense_nd_array_to_data_array( uri: str, *, dim_names: Tuple[str, ...], - chunks: Optional[Tuple[int, ...]] = None, - attrs: Optional[Mapping[str, Any]] = None, - context: Optional[SOMATileDBContext] = None, + chunks: Tuple[int, ...] | None = None, + attrs: Mapping[str, Any] | None = None, + context: SOMATileDBContext | None = None, ) -> xr.DataArray: """Create a :class:`xarray.DataArray` that accesses a SOMA :class:`DenseNDarray` through dask. diff --git a/apis/python/src/tiledbsoma/io/spatial/ingest.py b/apis/python/src/tiledbsoma/io/spatial/ingest.py index 5d43f4f539..1d9b9f187d 100644 --- a/apis/python/src/tiledbsoma/io/spatial/ingest.py +++ b/apis/python/src/tiledbsoma/io/spatial/ingest.py @@ -9,13 +9,14 @@ start from other formats. """ +from __future__ import annotations + import json import warnings from pathlib import Path from typing import ( TYPE_CHECKING, List, - Optional, Sequence, Tuple, Type, @@ -88,11 +89,11 @@ def path_validator(instance, attribute, value: Path) -> None: # type: ignore[no raise OSError(f"Path {value} does not exist") -def optional_path_converter(value: Optional[Union[str, Path]]) -> Optional[Path]: +def optional_path_converter(value: str | Path | None) -> Path | None: return None if value is None else Path(value) -def optional_path_validator(instance, attribute, x: Optional[Path]) -> None: # type: ignore[no-untyped-def] +def optional_path_validator(instance, attribute, x: Path | None) -> None: # type: ignore[no-untyped-def] if x is not None and not x.exists(): raise OSError(f"Path {x} does not exist") @@ -105,14 +106,14 @@ def from_base_folder( cls, base_path: Union[str, Path], *, - gene_expression: Optional[Union[str, Path]] = None, - scale_factors: Optional[Union[str, Path]] = None, - tissue_positions: Optional[Union[str, Path]] = None, - fullres_image: Optional[Union[str, Path]] = None, - hires_image: Optional[Union[str, Path]] = None, - lowres_image: Optional[Union[str, Path]] = None, + gene_expression: str | Path | None = None, + scale_factors: str | Path | None = None, + tissue_positions: str | Path | None = None, + fullres_image: str | Path | None = None, + hires_image: str | Path | None = None, + lowres_image: str | Path | None = None, use_raw_counts: bool = False, - version: Optional[Union[int, Tuple[int, int, int]]] = None, + version: int | Tuple[int, int, int] | None = None, ) -> Self: """Create ingestion files from Space Ranger output directory. @@ -164,12 +165,12 @@ def from_spatial_folder( spatial_dir: Union[str, Path], *, gene_expression: Union[str, Path], - scale_factors: Optional[Union[str, Path]] = None, - tissue_positions: Optional[Union[str, Path]] = None, - fullres_image: Optional[Union[str, Path]] = None, - hires_image: Optional[Union[str, Path]] = None, - lowres_image: Optional[Union[str, Path]] = None, - version: Optional[Union[int, Tuple[int, int, int]]] = None, + scale_factors: str | Path | None = None, + tissue_positions: str | Path | None = None, + fullres_image: str | Path | None = None, + hires_image: str | Path | None = None, + lowres_image: str | Path | None = None, + version: int | Tuple[int, int, int] | None = None, ) -> Self: """Create ingestion files from Space Ranger spatial output directory and the gene expression file. @@ -236,21 +237,21 @@ def from_spatial_folder( gene_expression: Path = attrs.field(converter=Path, validator=path_validator) scale_factors: Path = attrs.field(converter=Path, validator=path_validator) tissue_positions: Path = attrs.field(converter=Path, validator=path_validator) - fullres_image: Optional[Path] = attrs.field( + fullres_image: Path | None = attrs.field( converter=optional_path_converter, validator=optional_path_validator ) - hires_image: Optional[Path] = attrs.field( + hires_image: Path | None = attrs.field( converter=optional_path_converter, validator=optional_path_validator ) - lowres_image: Optional[Path] = attrs.field( + lowres_image: Path | None = attrs.field( converter=optional_path_converter, validator=optional_path_validator ) - version: Optional[Union[int, Tuple[int, int, int]]] = attrs.field(default=None) + version: int | Tuple[int, int, int] | None = attrs.field(default=None) @version.validator def _validate_version( # type: ignore[no-untyped-def] - self, attribute, value: Optional[Union[int, Tuple[int, int, int]]] + self, attribute, value: int | Tuple[int, int, int] | None ) -> None: major_version = value[0] if isinstance(value, tuple) else value if major_version is not None and major_version != 2: @@ -273,8 +274,8 @@ def from_visium( measurement_name: str, scene_name: str, *, - context: Optional["SOMATileDBContext"] = None, - platform_config: Optional["PlatformConfig"] = None, + context: "SOMATileDBContext | None" = None, + platform_config: "PlatformConfig | None" = None, obs_id_name: str = "obs_id", var_id_name: str = "var_id", X_layer_name: str = "data", @@ -282,10 +283,10 @@ def from_visium( image_name: str = "tissue", image_channel_first: bool = True, ingest_mode: IngestMode = "write", - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, X_kind: Union[Type[SparseNDArray], Type[DenseNDArray]] = SparseNDArray, - registration_mapping: Optional["ExperimentAmbientLabelMapping"] = None, - uns_keys: Optional[Sequence[str]] = None, + registration_mapping: "ExperimentAmbientLabelMapping | None" = None, + uns_keys: Sequence[str] | None = None, additional_metadata: "AdditionalMetadata" = None, use_raw_counts: bool = False, write_obs_spatial_presence: bool = False, @@ -464,7 +465,7 @@ def from_visium( # Create a list of image paths. # -- Each item contains: level name, image path, and scale factors to fullres. - image_paths: List[Tuple[str, Path, Optional[float]]] = [] + image_paths: List[Tuple[str, Path, float | None]] = [] if input_paths.fullres_image is not None: image_paths.append(("fullres", Path(input_paths.fullres_image), None)) if input_paths.hires_image is not None: @@ -626,8 +627,8 @@ def _write_scene_presence_dataframe( *, ingestion_params: IngestionParams, additional_metadata: "AdditionalMetadata" = None, - platform_config: Optional["PlatformConfig"] = None, - context: Optional["SOMATileDBContext"] = None, + platform_config: "PlatformConfig | None" = None, + context: "SOMATileDBContext | None" = None, ) -> DataFrame: s = _util.get_start_stamp() @@ -691,8 +692,8 @@ def _write_visium_spots( *, ingestion_params: IngestionParams, additional_metadata: "AdditionalMetadata" = None, - platform_config: Optional["PlatformConfig"] = None, - context: Optional["SOMATileDBContext"] = None, + platform_config: "PlatformConfig | None" = None, + context: "SOMATileDBContext | None" = None, ) -> PointCloudDataFrame: """Creates, opens, and writes data to a ``PointCloudDataFrame`` with the spot locations and metadata. Returns the open dataframe for writing. @@ -753,7 +754,7 @@ def _create_or_open_scene( uri: str, *, ingestion_params: IngestionParams, - context: Optional["SOMATileDBContext"], + context: "SOMATileDBContext | None", additional_metadata: "AdditionalMetadata" = None, ) -> Scene: """Creates or opens a ``Scene`` and returns it open for writing.""" @@ -773,14 +774,14 @@ def _create_or_open_scene( def _create_visium_tissue_images( uri: str, - image_paths: List[Tuple[str, Path, Optional[float]]], + image_paths: List[Tuple[str, Path, float | None]], *, image_channel_first: bool, additional_metadata: "AdditionalMetadata" = None, - platform_config: Optional["PlatformConfig"] = None, - context: Optional["SOMATileDBContext"] = None, + platform_config: "PlatformConfig | None" = None, + context: "SOMATileDBContext | None" = None, ingestion_params: IngestionParams, - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, ) -> MultiscaleImage: """Creates, opens, and writes a ``MultiscaleImage`` with the provide visium resolutions levels and returns the open image for writing. diff --git a/apis/python/src/tiledbsoma/io/spatial/outgest.py b/apis/python/src/tiledbsoma/io/spatial/outgest.py index 449d2f0b2b..032018700e 100644 --- a/apis/python/src/tiledbsoma/io/spatial/outgest.py +++ b/apis/python/src/tiledbsoma/io/spatial/outgest.py @@ -2,8 +2,11 @@ # Copyright (c) 2024 TileDB, Inc # # Licensed under the MIT License. + +from __future__ import annotations + import warnings -from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Mapping, Sequence, Tuple, Union import pandas as pd import somacore @@ -38,7 +41,7 @@ def _convert_axis_names( coord_axis_names: Tuple[str, ...], - data_axis_names: Optional[Tuple[str, ...]] = None, + data_axis_names: Tuple[str, ...] | None = None, ) -> Tuple[Tuple[str, ...], Dict[str, str]]: """Convert SOMA axis names to SpatialData axis names. @@ -108,7 +111,7 @@ def to_spatialdata_points( key: str, scene_id: str, scene_dim_map: Dict[str, str], - transform: Optional[somacore.CoordinateTransform], + transform: somacore.CoordinateTransform | None, soma_joinid_name: str, ) -> dd.DataFrame: """Export a :class:`PointCloudDataFrame` to a :class:`spatialdata.ShapesModel. @@ -152,7 +155,7 @@ def to_spatialdata_shapes( key: str, scene_id: str, scene_dim_map: Dict[str, str], - transform: Optional[somacore.CoordinateTransform], + transform: somacore.CoordinateTransform | None, soma_joinid_name: str, ) -> gpd.GeoDataFrame: """Export a :class:`PointCloudDataFrame` to a :class:`spatialdata.ShapesModel. @@ -222,12 +225,12 @@ def to_spatialdata_shapes( def to_spatialdata_image( image: MultiscaleImage, - level: Optional[Union[str, int]] = None, + level: Union[str, int] | None = None, *, key: str, scene_id: str, scene_dim_map: Dict[str, str], - transform: Optional[somacore.CoordinateTransform], + transform: somacore.CoordinateTransform | None, ) -> "DataArray": """Export a level of a :class:`MultiscaleImage` to a :class:`spatialdata.Image2DModel` or :class:`spatialdata.Image3DModel`. @@ -316,7 +319,7 @@ def to_spatialdata_multiscale_image( key: str, scene_id: str, scene_dim_map: Dict[str, str], - transform: Optional[somacore.CoordinateTransform], + transform: somacore.CoordinateTransform | None, ) -> "DataTree": """Export a MultiscaleImage to a DataTree. @@ -412,7 +415,7 @@ def to_spatialdata_multiscale_image( def _get_transform_from_collection( key: str, metadata: Mapping[str, Any] -) -> Optional[somacore.CoordinateTransform]: +) -> somacore.CoordinateTransform | None: transform_key = f"soma_scene_registry_{key}" if transform_key in metadata: transform_json = metadata[transform_key] @@ -427,7 +430,7 @@ def _add_scene_to_spatialdata( *, obs_id_name: str, var_id_name: str, - measurement_names: Optional[Sequence[str]] = None, + measurement_names: Sequence[str] | None = None, ) -> None: """Adds items from a Scene to a SpatialData object. @@ -547,11 +550,11 @@ def _add_scene_to_spatialdata( def to_spatialdata( experiment: Experiment, *, - measurement_names: Optional[Sequence[str]] = None, - scene_names: Optional[Sequence[str]] = None, + measurement_names: Sequence[str] | None = None, + scene_names: Sequence[str] | None = None, obs_id_name: str = "obs_id", var_id_name: str = "var_id", - table_kwargs: Optional[Mapping[str, Dict[str, Any]]] = None, + table_kwargs: Mapping[str, Dict[str, Any]] | None = None, ) -> sd.SpatialData: """Converts the experiment group to SpatialData format. diff --git a/apis/python/src/tiledbsoma/io/update_uns.py b/apis/python/src/tiledbsoma/io/update_uns.py index f16b73682a..a6b7bb17f7 100644 --- a/apis/python/src/tiledbsoma/io/update_uns.py +++ b/apis/python/src/tiledbsoma/io/update_uns.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from os.path import join -from typing import Literal, Optional +from typing import Literal import numpy as np import pandas as pd @@ -26,11 +28,11 @@ def update_uns_by_uri( uns: UnsMapping, measurement_name: str, *, - use_relative_uri: Optional[bool] = None, - context: Optional[SOMATileDBContext] = None, + use_relative_uri: bool | None = None, + context: SOMATileDBContext | None = None, additional_metadata: AdditionalMetadata = None, - platform_config: Optional[PlatformConfig] = None, - default_index_name: Optional[str] = None, + platform_config: PlatformConfig | None = None, + default_index_name: str | None = None, strict: Strict = True, ) -> None: """Wrapper around :func:`_update_uns` that opens the experiment at the given URI. @@ -61,11 +63,11 @@ def _update_uns( uns: UnsMapping, measurement_name: str, *, - use_relative_uri: Optional[bool] = None, - context: Optional[SOMATileDBContext] = None, + use_relative_uri: bool | None = None, + context: SOMATileDBContext | None = None, additional_metadata: AdditionalMetadata = None, - platform_config: Optional[PlatformConfig] = None, - default_index_name: Optional[str] = None, + platform_config: PlatformConfig | None = None, + default_index_name: str | None = None, strict: Strict = True, ) -> None: """ @@ -142,8 +144,8 @@ def _update_uns_dict( uns: UnsMapping, *, ingest_platform_ctx: IngestPlatformCtx, - use_relative_uri: Optional[bool], - default_index_name: Optional[str], + use_relative_uri: bool | None, + default_index_name: str | None, strict: Strict, ) -> None: for k, v in uns.items(): diff --git a/apis/python/src/tiledbsoma/logging.py b/apis/python/src/tiledbsoma/logging.py index 4c00810ebd..183f05c785 100644 --- a/apis/python/src/tiledbsoma/logging.py +++ b/apis/python/src/tiledbsoma/logging.py @@ -3,8 +3,9 @@ # # Licensed under the MIT License. +from __future__ import annotations + import logging -from typing import Optional logger = logging.getLogger("tiledbsoma") @@ -55,7 +56,7 @@ def log_io_same(message: str) -> None: log_io(message, message) -def log_io(info_message: Optional[str], debug_message: str) -> None: +def log_io(info_message: str | None, debug_message: str) -> None: """Data-ingestion timeframes range widely. Some folks won't want details for smaller uploads; some will want details for larger ones. For I/O and for I/O only, it's helpful to print a short message at INFO level, diff --git a/apis/python/src/tiledbsoma/options/_tiledb_create_write_options.py b/apis/python/src/tiledbsoma/options/_tiledb_create_write_options.py index fa0277a525..5166aee1df 100644 --- a/apis/python/src/tiledbsoma/options/_tiledb_create_write_options.py +++ b/apis/python/src/tiledbsoma/options/_tiledb_create_write_options.py @@ -1,9 +1,10 @@ +from __future__ import annotations + from typing import ( Any, Dict, Iterable, Mapping, - Optional, Sequence, Tuple, Type, @@ -66,17 +67,17 @@ def _normalize_filters(inputs: Iterable[_FilterSpec]) -> Tuple[_DictFilterSpec, # This exists because mypy does not currently (v1.3) support complex converters # like converters.optional(inner_converter). def _normalize_filters_optional( - inputs: Optional[Iterable[_FilterSpec]], -) -> Optional[Tuple[_DictFilterSpec, ...]]: + inputs: Iterable[_FilterSpec] | None, +) -> Tuple[_DictFilterSpec, ...] | None: return None if inputs is None else _normalize_filters(inputs) @attrs_.define(frozen=True, slots=True) class _ColumnConfig: - filters: Optional[Tuple[_DictFilterSpec, ...]] = attrs_.field( + filters: Tuple[_DictFilterSpec, ...] | None = attrs_.field( converter=_normalize_filters_optional ) - tile: Optional[int] = attrs_.field(validator=vld.optional(vld.instance_of(int))) + tile: int | None = attrs_.field(validator=vld.optional(vld.instance_of(int))) @classmethod def from_dict(cls, input: _DictColumnSpec) -> Self: @@ -132,17 +133,17 @@ class TileDBCreateOptions: "ZstdFilter", ), ) - validity_filters: Optional[Tuple[_DictFilterSpec, ...]] = attrs_.field( + validity_filters: Tuple[_DictFilterSpec, ...] | None = attrs_.field( converter=_normalize_filters_optional, default=None ) allows_duplicates: bool = attrs_.field( validator=vld.instance_of(bool), default=False, ) - tile_order: Optional[str] = attrs_.field( + tile_order: str | None = attrs_.field( validator=vld.optional(vld.instance_of(str)), default=None ) - cell_order: Optional[str] = attrs_.field( + cell_order: str | None = attrs_.field( validator=vld.optional(vld.instance_of(str)), default=None ) dims: Mapping[str, _ColumnConfig] = attrs_.field( @@ -176,7 +177,7 @@ def from_platform_config( return cls(**filtered_create_entry) return create_entry - def cell_tile_orders(self) -> Tuple[Optional[str], Optional[str]]: + def cell_tile_orders(self) -> Tuple[str | None, str | None]: """Returns the cell and tile orders that should be used. If *neither* ``cell_order`` nor ``tile_order`` is present, only in this @@ -200,7 +201,7 @@ class TileDBWriteOptions: """Tuning options used when writing to SOMA arrays.""" sort_coords: bool = attrs_.field(validator=vld.instance_of(bool), default=True) - consolidate_and_vacuum: Optional[bool] = attrs_.field( + consolidate_and_vacuum: bool | None = attrs_.field( validator=vld.instance_of(bool), default=False ) diff --git a/apis/python/tests/_util.py b/apis/python/tests/_util.py index eff4dd57bf..391206a8cc 100644 --- a/apis/python/tests/_util.py +++ b/apis/python/tests/_util.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import re from contextlib import contextmanager, nullcontext from pathlib import Path -from typing import Any, List, Optional, Tuple, Type, Union +from typing import Any, List, Tuple, Type, Union import numpy as np import pandas as pd @@ -101,7 +103,7 @@ def assert_transform_equal( assert False -def parse_col(col_str: str) -> Tuple[Optional[str], List[str]]: +def parse_col(col_str: str) -> Tuple[str | None, List[str]]: """Parse a "column string" of the form ``val1,val2,...`` or ``name=val1,val2,...``.""" pcs = col_str.split("=") if len(pcs) == 1: @@ -114,7 +116,7 @@ def parse_col(col_str: str) -> Tuple[Optional[str], List[str]]: raise ValueError(f"Invalid column string: {col_str}") -def make_pd_df(index_str: Optional[str] = None, **cols) -> pd.DataFrame: +def make_pd_df(index_str: str | None = None, **cols) -> pd.DataFrame: """DataFrame construction helper, for tests. - index and columns are provided as strings of the form ``name=val1,val2,...``. @@ -153,7 +155,7 @@ def raises_no_typeguard(exc: Type[Exception], *args: Any, **kwargs: Any): def maybe_raises( - expected_exception: Optional[Err], + expected_exception: Err | None, *args: Any, **kwargs: Any, ) -> Union[RaisesContext[E], ExceptionInfo[E], nullcontext]: @@ -181,7 +183,7 @@ def maybe_raises( return nullcontext() -def verify_logs(caplog: LogCaptureFixture, expected_logs: Optional[List[str]]) -> None: +def verify_logs(caplog: LogCaptureFixture, expected_logs: List[str] | None) -> None: """Verify that expected log messages are present in a pytest "caplog" (captured logs) fixture.""" if not expected_logs: return diff --git a/apis/python/tests/test_basic_anndata_io.py b/apis/python/tests/test_basic_anndata_io.py index f2c80b3a7a..681ce88664 100644 --- a/apis/python/tests/test_basic_anndata_io.py +++ b/apis/python/tests/test_basic_anndata_io.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import json import random import tempfile from copy import deepcopy from pathlib import Path -from typing import Optional, Tuple +from typing import Tuple import anndata import numpy as np @@ -527,7 +529,7 @@ def add_X_layer( X_layer_name: str, X_layer_data, # E.g. a scipy.csr_matrix from scanpy analysis ingest_mode: str = "write", - use_relative_uri: Optional[bool] = None, + use_relative_uri: bool | None = None, ) -> None: if exp.closed or exp.mode != "w": raise tiledbsoma.SOMAError(f"Experiment must be open for write: {exp.uri}") @@ -547,8 +549,8 @@ def add_matrix_to_collection( matrix_name: str, matrix_data, # E.g. a scipy.csr_matrix from scanpy analysis ingest_mode: str = "write", - use_relative_uri: Optional[bool] = None, - context: Optional[tiledbsoma.SOMATileDBContext] = None, + use_relative_uri: bool | None = None, + context: tiledbsoma.SOMATileDBContext | None = None, ) -> None: # For local disk and S3, creation and storage URIs are identical. For # cloud, creation URIs look like tiledb://namespace/s3://bucket/path/to/obj @@ -993,7 +995,7 @@ def test_id_names(tmp_path, obs_id_name, var_id_name, indexify_obs, indexify_var def make_uns_adata( tmp_path: Path, measurement_name: str = "RNA", - uns: Optional[UnsMapping] = None, + uns: UnsMapping | None = None, ) -> Tuple[str, AnnData]: obs = pd.DataFrame( data={"obs_id": np.asarray(["a", "b", "c"])}, diff --git a/apis/python/tests/test_dataframe_io_roundtrips.py b/apis/python/tests/test_dataframe_io_roundtrips.py index fadf3c234f..b04b7f9d9e 100644 --- a/apis/python/tests/test_dataframe_io_roundtrips.py +++ b/apis/python/tests/test_dataframe_io_roundtrips.py @@ -1,12 +1,14 @@ # The tests in this file verify issues where an ingest/outgest "round trip" modifies an AnnData's # "obs" or "var" DataFrames. See https://github.com/single-cell-data/TileDB-SOMA/issues/2829 for more info. +from __future__ import annotations + import json from copy import deepcopy from dataclasses import dataclass from os.path import join from pathlib import Path -from typing import List, Optional +from typing import List import numpy as np import pandas as pd @@ -38,9 +40,9 @@ class RoundTrip: # also exist. persisted_column_names: List[str] # Expected "original index metadata" (attached to the persisted SOMA DataFrame) - persisted_metadata: Optional[str] = None + persisted_metadata: str | None = None # Argument passed to `_write_dataframe` on ingest (default here matches `from_anndata`'s "obs" path) - ingest_id_column_name: Optional[str] = "obs_id" + ingest_id_column_name: str | None = "obs_id" # fmt: off @@ -108,7 +110,7 @@ class RoundTrip: def verify_metadata( - sdf: DataFrame, persisted_column_names: List[str], persisted_metadata: Optional[str] + sdf: DataFrame, persisted_column_names: List[str], persisted_metadata: str | None ): # Verify column names and types schema = sdf.schema @@ -130,8 +132,8 @@ def test_adata_io_roundtrips( tmp_path: Path, original_df: pd.DataFrame, persisted_column_names: List[str], - persisted_metadata: Optional[str], - ingest_id_column_name: Optional[str], + persisted_metadata: str | None, + ingest_id_column_name: str | None, outgested_df: pd.DataFrame, ): """Given an `original_df`, set it as the `obs` DataFrame of an AnnData, ingest it, outgest it back, and compare the @@ -172,8 +174,8 @@ def test_df_io_roundtrips( tmp_path: Path, original_df: pd.DataFrame, persisted_column_names: List[str], - persisted_metadata: Optional[str], - ingest_id_column_name: Optional[str], + persisted_metadata: str | None, + ingest_id_column_name: str | None, outgested_df: pd.DataFrame, ): uri = str(tmp_path) diff --git a/apis/python/tests/test_registration_mappings.py b/apis/python/tests/test_registration_mappings.py index 3247f4bf19..c3c58c0025 100644 --- a/apis/python/tests/test_registration_mappings.py +++ b/apis/python/tests/test_registration_mappings.py @@ -2,10 +2,12 @@ Test join-id registrations for ingesting multiple AnnData objects into a single SOMA Experiment. """ +from __future__ import annotations + import math import tempfile from contextlib import nullcontext -from typing import List, Optional, Sequence, Tuple, Union +from typing import List, Sequence, Tuple, Union import anndata as ad import numpy as np @@ -26,7 +28,7 @@ def _create_anndata( obs_field_name: str, var_field_name: str, X_value_base: int, - raw_var_ids: Optional[Sequence[str]] = None, + raw_var_ids: Sequence[str] | None = None, ): n_obs = len(obs_ids) n_var = len(var_ids) @@ -237,7 +239,7 @@ def soma_larger(anndata_larger): ] ) def test_pandas_indexing( - index_col_and_name: Union[Tuple[Optional[str]], Tuple[str, Optional[str]]], + index_col_and_name: Union[Tuple[str | None], Tuple[str, str | None]], default_index_name: str, signature_col_names: List[Union[str, Tuple[str, str]]], ): diff --git a/apis/python/tests/test_reindexer_api.py b/apis/python/tests/test_reindexer_api.py index 1e876397ff..1745f8054c 100644 --- a/apis/python/tests/test_reindexer_api.py +++ b/apis/python/tests/test_reindexer_api.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations import numpy as np import pytest @@ -7,7 +7,7 @@ @pytest.mark.parametrize("context", [None, SOMATileDBContext()]) -def test_reindexer_api(context: Optional[SOMATileDBContext]): +def test_reindexer_api(context: SOMATileDBContext | None): keys = np.arange(3, 10, 2) ids = np.arange(3, 10, 2) expected = np.array([0, 1, 2, 3]) diff --git a/apis/python/tests/test_update_dataframes.py b/apis/python/tests/test_update_dataframes.py index 58fe14315d..891fbd1058 100644 --- a/apis/python/tests/test_update_dataframes.py +++ b/apis/python/tests/test_update_dataframes.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import tempfile from dataclasses import dataclass -from typing import Optional, Type +from typing import Type import numpy as np import pandas as pd @@ -127,7 +129,7 @@ def verify_updates( experiment_path, obs, var, - exc: Optional[Type[ValueError]] = None, + exc: Type[ValueError] | None = None, ): """ Calls `update_obs` and `update_var` on the experiment. Also verifies that the diff --git a/apis/python/tests/test_update_uns.py b/apis/python/tests/test_update_uns.py index 56ffecfe68..d5ccdbb4db 100644 --- a/apis/python/tests/test_update_uns.py +++ b/apis/python/tests/test_update_uns.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import logging from copy import deepcopy from dataclasses import dataclass from pathlib import Path -from typing import Dict, List, Optional, Union +from typing import Dict, List, Union import numpy as np from _pytest.logging import LogCaptureFixture @@ -17,7 +19,7 @@ from tests.test_basic_anndata_io import TEST_UNS, make_uns_adata ValidUpdates = Union[None, str, List[str], Dict[str, "ValidUpdates"]] -Logs = Optional[List[str]] +Logs = Union[List[str], None] @dataclass @@ -43,14 +45,14 @@ class Case: id: str uns_updates: UnsMapping strict: Strict = True - err: Optional[Err] = None + err: Err | None = None valid_updates: ValidUpdates = None logs: Logs = None def case( id: str, - err: Optional[Err] = None, + err: Err | None = None, valid_updates: ValidUpdates = None, logs: Logs = None, strict: Strict = True, @@ -175,7 +177,7 @@ def test_update_uns( tmp_path: Path, uns_updates: UnsDict, strict: Strict, - err: Optional[Err], + err: Err | None, valid_updates: ValidUpdates, logs: Logs, ): diff --git a/apis/python/version.py b/apis/python/version.py index 959440f622..a8c14772ec 100644 --- a/apis/python/version.py +++ b/apis/python/version.py @@ -58,6 +58,8 @@ git tag -s v1.0 """ +from __future__ import annotations + __author__ = ( "Douglas Creager ", "Michal Nazarewicz ", @@ -75,7 +77,7 @@ from datetime import date from os.path import basename, dirname, join, relpath from subprocess import DEVNULL, CalledProcessError, check_output -from typing import List, Optional +from typing import List GIT_RELPATH = "apis/python/version.py" RELEASE_VERSION_FILE = join(dirname(__file__), "RELEASE-VERSION") @@ -95,7 +97,7 @@ def err(*args, **kwargs): def lines( *cmd, drop_trailing_newline: bool = True, stderr=DEVNULL, **kwargs -) -> Optional[List[str]]: +) -> List[str] | None: """Run a command, return its stdout as a list of lines. Strip each line's trailing newline, and drop the last line if it's empty, by default. @@ -114,7 +116,7 @@ def lines( return lns -def line(*cmd, **kwargs) -> Optional[str]: +def line(*cmd, **kwargs) -> str | None: """Verify a command produces exactly one line of stdout, and return it, otherwise `None`.""" lns = lines(*cmd, **kwargs) if lns is None: @@ -125,13 +127,13 @@ def line(*cmd, **kwargs) -> Optional[str]: return lns[0] -def get_latest_tag() -> Optional[str]: +def get_latest_tag() -> str | None: """Return the most recent local Git tag of the form `[0-9].*.*` (or `None` if none exist).""" tags = lines("git", "tag", "--list", "--sort=v:refname", "[0-9].*.*") return tags[-1] if tags else None -def get_latest_remote_tag(remote: str) -> Optional[str]: +def get_latest_remote_tag(remote: str) -> str | None: """Return the most recent Git tag of the form `[0-9].*.*`, from a remote Git repository.""" tags = lines("git", "ls-remote", "--tags", "--sort=v:refname", remote, "[0-9].*.*") if not tags: @@ -148,7 +150,7 @@ def get_sha_base10() -> int: return int(sha, 16) -def get_default_remote() -> Optional[str]: +def get_default_remote() -> str | None: """Find a Git remote to parse a most recent release tag from. - If the current branch tracks a remote branch, use that remote @@ -171,7 +173,7 @@ def get_default_remote() -> Optional[str]: return None -def get_git_version() -> Optional[str]: +def get_git_version() -> str | None: """Construct a PEP440-compatible version string that encodes various Git state. - If `git describe` returns a plain release tag, use that. @@ -245,7 +247,7 @@ def get_git_version() -> Optional[str]: return ver -def read_release_version() -> Optional[str]: +def read_release_version() -> str | None: try: with open(RELEASE_VERSION_FILE) as fd: ver = fd.readline().strip()