prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  multi_index not accepting tuples
  The [UDF apply docs](https://docs.tiledb.com/cloud/client-api/serverless-udfs#multi-index-usage) imply the tuples and slices are interchangeable.  The standard API throws an exception on tuples (for both tiledb: and s3:)
  
  
  ```
  >>> import tiledb, tiledb.cloud
  >>> A = tiledb.DenseArray("tiledb://TileDB-Inc/quickstart_dense", ctx=tiledb.cloud.Ctx())
  >>> A.multi_index[[(1,2), 4], [slice(1,4)]]['a']
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/home/ec2-user/venv/lib64/python3.7/site-packages/tiledb/multirange_indexing.py", line 121, in __getitem__
      ranges = self.getitem_ranges(idx)
    File "/home/ec2-user/venv/lib64/python3.7/site-packages/tiledb/multirange_indexing.py", line 109, in getitem_ranges
      subranges = sel_to_subranges(sel)
    File "/home/ec2-user/venv/lib64/python3.7/site-packages/tiledb/multirange_indexing.py", line 60, in sel_to_subranges
      raise TypeError("Unsupported selection ")
  TypeError: Unsupported selection 
  ```
  
  It would be great if multi_index and UDF apply accepted the same index types.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `tiledb/multirange_indexing.py`
  Content:
  ```
  1 import tiledb
  2 from tiledb import Array, ArraySchema
  3 import os, numpy as np
  4 import sys, weakref
  5 
  6 try:
  7     from tiledb.libtiledb import multi_index
  8 except:
  9     from tiledb.indexing import multi_index
  10 
  11 def _index_as_tuple(idx):
  12     """Forces scalar index objects to a tuple representation"""
  13     if isinstance(idx, tuple):
  14         return idx
  15     return (idx,)
  16 
  17 def mr_dense_result_shape(ranges, base_shape = None):
  18     # assumptions: len(ranges) matches number of dims
  19     if base_shape is not None:
  20         assert len(ranges) == len(base_shape), "internal error: mismatched shapes"
  21 
  22     new_shape = list()
  23     for i,rr in enumerate(ranges):
  24         if rr != ():
  25             m = list(map(lambda y: abs(y[1] - y[0]) + 1, rr))
  26             new_shape.append(np.sum(m))
  27         else:
  28             if base_shape is None:
  29                 raise ValueError("Missing required base_shape for whole-dimension slices")
  30             # empty range covers dimension
  31             new_shape.append(base_shape[i])
  32 
  33     return tuple(new_shape)
  34 
  35 def mr_dense_result_numel(ranges):
  36     return np.prod(mr_dense_result_shape(ranges))
  37 
  38 def sel_to_subranges(dim_sel):
  39     if isinstance(dim_sel, list):
  40         dim_sel = tuple(dim_sel)
  41     elif not isinstance(dim_sel, tuple):
  42         dim_sel = (dim_sel,)
  43 
  44     subranges = list()
  45     for range in dim_sel:
  46         if np.isscalar(range):
  47             subranges.append( (range, range) )
  48         elif isinstance(range, slice):
  49             if range.step is not None:
  50                 raise ValueError("Stepped slice ranges are not supported")
  51             elif range.start is None and range.stop is None:
  52                 # ':' full slice
  53                 pass
  54             else:
  55                 subranges.append( (range.start, range.stop) )
  56         elif isinstance(range, list):
  57             for el in range:
  58                 subranges.append( (el, el) )
  59         else:
  60             raise TypeError("Unsupported selection ")
  61 
  62     return tuple(subranges)
  63 
  64 
  65 class MultiRangeIndexer(object):
  66     """
  67     Implements multi-range / outer / orthogonal indexing.
  68 
  69     """
  70     # for cython
  71     # comment out for Python 2 :/
  72     #array: Array
  73     #schema: ArraySchema
  74     #def __init__(self, array: Array, query = None):
  75 
  76     def __init__(self, array, query = None):
  77         if not issubclass(type(array), tiledb.Array):
  78             raise ValueError("Internal error: MultiRangeIndexer expected tiledb.Array")
  79         self.array_ref = weakref.ref(array)
  80         self.schema = array.schema
  81         self.query = query
  82 
  83     @property
  84     def array(self):
  85         assert self.array_ref() is not None, \
  86             "Internal error: invariant violation (indexing call w/ dead array_ref)"
  87         return self.array_ref()
  88 
  89     @classmethod
  90     def __test_init__(cls, array):
  91         """
  92         Internal helper method for testing getitem range calculation.
  93         :param array:
  94         :return:
  95         """
  96         m = cls.__new__(cls)
  97         m.array_ref = weakref.ref(array)
  98         m.schema = array.schema
  99         m.query = None
  100         return m
  101 
  102     def getitem_ranges(self, idx):
  103         dom = self.schema.domain
  104         ndim = dom.ndim
  105         idx = _index_as_tuple(idx)
  106 
  107         ranges = list()
  108         for i,sel in enumerate(idx):
  109             subranges = sel_to_subranges(sel)
  110             ranges.append(subranges)
  111 
  112         # extend the list to ndim
  113         if len(ranges) < ndim:
  114             ranges.extend([ tuple() for _ in range(ndim-len(ranges))])
  115 
  116         rval = tuple(ranges)
  117         return rval
  118 
  119     def __getitem__(self, idx):
  120         # implements multi-range / outer / orthogonal indexing
  121         ranges = self.getitem_ranges(idx)
  122 
  123         dom = self.schema.domain
  124         attr_names = tuple(self.schema.attr(i).name for i in range(self.schema.nattr))
  125 
  126         coords = None
  127         if self.query is not None:
  128             # if we are called via Query object, then we need to respect Query semantics
  129             attr_names = tuple(self.query.attrs) if self.query.attrs else attr_names # query.attrs might be None -> all
  130             coords = self.query.coords
  131 
  132         # TODO order
  133         result_dict = multi_index(
  134             self.array,
  135             attr_names,
  136             ranges,
  137             coords=coords
  138         )
  139 
  140         if self.schema.sparse:
  141             return result_dict
  142         else:
  143             result_shape = mr_dense_result_shape(ranges, self.schema.shape)
  144             for arr in result_dict.values():
  145                 # TODO check/test layout
  146                 arr.shape = result_shape
  147             return result_dict
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/tiledb/multirange_indexing.py b/tiledb/multirange_indexing.py
  --- a/tiledb/multirange_indexing.py
  +++ b/tiledb/multirange_indexing.py
  @@ -8,11 +8,6 @@
   except:
       from tiledb.indexing import multi_index
   
  -def _index_as_tuple(idx):
  -    """Forces scalar index objects to a tuple representation"""
  -    if isinstance(idx, tuple):
  -        return idx
  -    return (idx,)
   
   def mr_dense_result_shape(ranges, base_shape = None):
       # assumptions: len(ranges) matches number of dims
  @@ -36,11 +31,6 @@
       return np.prod(mr_dense_result_shape(ranges))
   
   def sel_to_subranges(dim_sel):
  -    if isinstance(dim_sel, list):
  -        dim_sel = tuple(dim_sel)
  -    elif not isinstance(dim_sel, tuple):
  -        dim_sel = (dim_sel,)
  -
       subranges = list()
       for range in dim_sel:
           if np.isscalar(range):
  @@ -53,6 +43,8 @@
                   pass
               else:
                   subranges.append( (range.start, range.stop) )
  +        elif isinstance(range, tuple):
  +            subranges.extend((range,))
           elif isinstance(range, list):
               for el in range:
                   subranges.append( (el, el) )
  @@ -102,10 +94,16 @@
       def getitem_ranges(self, idx):
           dom = self.schema.domain
           ndim = dom.ndim
  -        idx = _index_as_tuple(idx)
  +
  +        if isinstance(idx, tuple):
  +            idx = list(idx)
  +        else:
  +            idx = [idx]
   
           ranges = list()
           for i,sel in enumerate(idx):
  +            if not isinstance(sel, list):
  +                sel = [sel]
               subranges = sel_to_subranges(sel)
               ranges.append(subranges)
  
verification_info: {"golden_diff": "diff --git a/tiledb/multirange_indexing.py b/tiledb/multirange_indexing.py\n--- a/tiledb/multirange_indexing.py\n+++ b/tiledb/multirange_indexing.py\n@@ -8,11 +8,6 @@\n except:\n     from tiledb.indexing import multi_index\n \n-def _index_as_tuple(idx):\n-    \"\"\"Forces scalar index objects to a tuple representation\"\"\"\n-    if isinstance(idx, tuple):\n-        return idx\n-    return (idx,)\n \n def mr_dense_result_shape(ranges, base_shape = None):\n     # assumptions: len(ranges) matches number of dims\n@@ -36,11 +31,6 @@\n     return np.prod(mr_dense_result_shape(ranges))\n \n def sel_to_subranges(dim_sel):\n-    if isinstance(dim_sel, list):\n-        dim_sel = tuple(dim_sel)\n-    elif not isinstance(dim_sel, tuple):\n-        dim_sel = (dim_sel,)\n-\n     subranges = list()\n     for range in dim_sel:\n         if np.isscalar(range):\n@@ -53,6 +43,8 @@\n                 pass\n             else:\n                 subranges.append( (range.start, range.stop) )\n+        elif isinstance(range, tuple):\n+            subranges.extend((range,))\n         elif isinstance(range, list):\n             for el in range:\n                 subranges.append( (el, el) )\n@@ -102,10 +94,16 @@\n     def getitem_ranges(self, idx):\n         dom = self.schema.domain\n         ndim = dom.ndim\n-        idx = _index_as_tuple(idx)\n+\n+        if isinstance(idx, tuple):\n+            idx = list(idx)\n+        else:\n+            idx = [idx]\n \n         ranges = list()\n         for i,sel in enumerate(idx):\n+            if not isinstance(sel, list):\n+                sel = [sel]\n             subranges = sel_to_subranges(sel)\n             ranges.append(subranges)\n", "issue": "multi_index not accepting tuples\nThe [UDF apply docs](https://docs.tiledb.com/cloud/client-api/serverless-udfs#multi-index-usage) imply the tuples and slices are interchangeable.  The standard API throws an exception on tuples (for both tiledb: and s3:)\r\n\r\n\r\n```\r\n>>> import tiledb, tiledb.cloud\r\n>>> A = tiledb.DenseArray(\"tiledb://TileDB-Inc/quickstart_dense\", ctx=tiledb.cloud.Ctx())\r\n>>> A.multi_index[[(1,2), 4], [slice(1,4)]]['a']\r\nTraceback (most recent call last):\r\n  File \"<stdin>\", line 1, in <module>\r\n  File \"/home/ec2-user/venv/lib64/python3.7/site-packages/tiledb/multirange_indexing.py\", line 121, in __getitem__\r\n    ranges = self.getitem_ranges(idx)\r\n  File \"/home/ec2-user/venv/lib64/python3.7/site-packages/tiledb/multirange_indexing.py\", line 109, in getitem_ranges\r\n    subranges = sel_to_subranges(sel)\r\n  File \"/home/ec2-user/venv/lib64/python3.7/site-packages/tiledb/multirange_indexing.py\", line 60, in sel_to_subranges\r\n    raise TypeError(\"Unsupported selection \")\r\nTypeError: Unsupported selection \r\n```\r\n\r\nIt would be great if multi_index and UDF apply accepted the same index types.\n", "before_files": [{"content": "import tiledb\nfrom tiledb import Array, ArraySchema\nimport os, numpy as np\nimport sys, weakref\n\ntry:\n    from tiledb.libtiledb import multi_index\nexcept:\n    from tiledb.indexing import multi_index\n\ndef _index_as_tuple(idx):\n    \"\"\"Forces scalar index objects to a tuple representation\"\"\"\n    if isinstance(idx, tuple):\n        return idx\n    return (idx,)\n\ndef mr_dense_result_shape(ranges, base_shape = None):\n    # assumptions: len(ranges) matches number of dims\n    if base_shape is not None:\n        assert len(ranges) == len(base_shape), \"internal error: mismatched shapes\"\n\n    new_shape = list()\n    for i,rr in enumerate(ranges):\n        if rr != ():\n            m = list(map(lambda y: abs(y[1] - y[0]) + 1, rr))\n            new_shape.append(np.sum(m))\n        else:\n            if base_shape is None:\n                raise ValueError(\"Missing required base_shape for whole-dimension slices\")\n            # empty range covers dimension\n            new_shape.append(base_shape[i])\n\n    return tuple(new_shape)\n\ndef mr_dense_result_numel(ranges):\n    return np.prod(mr_dense_result_shape(ranges))\n\ndef sel_to_subranges(dim_sel):\n    if isinstance(dim_sel, list):\n        dim_sel = tuple(dim_sel)\n    elif not isinstance(dim_sel, tuple):\n        dim_sel = (dim_sel,)\n\n    subranges = list()\n    for range in dim_sel:\n        if np.isscalar(range):\n            subranges.append( (range, range) )\n        elif isinstance(range, slice):\n            if range.step is not None:\n                raise ValueError(\"Stepped slice ranges are not supported\")\n            elif range.start is None and range.stop is None:\n                # ':' full slice\n                pass\n            else:\n                subranges.append( (range.start, range.stop) )\n        elif isinstance(range, list):\n            for el in range:\n                subranges.append( (el, el) )\n        else:\n            raise TypeError(\"Unsupported selection \")\n\n    return tuple(subranges)\n\n\nclass MultiRangeIndexer(object):\n    \"\"\"\n    Implements multi-range / outer / orthogonal indexing.\n\n    \"\"\"\n    # for cython\n    # comment out for Python 2 :/\n    #array: Array\n    #schema: ArraySchema\n    #def __init__(self, array: Array, query = None):\n\n    def __init__(self, array, query = None):\n        if not issubclass(type(array), tiledb.Array):\n            raise ValueError(\"Internal error: MultiRangeIndexer expected tiledb.Array\")\n        self.array_ref = weakref.ref(array)\n        self.schema = array.schema\n        self.query = query\n\n    @property\n    def array(self):\n        assert self.array_ref() is not None, \\\n            \"Internal error: invariant violation (indexing call w/ dead array_ref)\"\n        return self.array_ref()\n\n    @classmethod\n    def __test_init__(cls, array):\n        \"\"\"\n        Internal helper method for testing getitem range calculation.\n        :param array:\n        :return:\n        \"\"\"\n        m = cls.__new__(cls)\n        m.array_ref = weakref.ref(array)\n        m.schema = array.schema\n        m.query = None\n        return m\n\n    def getitem_ranges(self, idx):\n        dom = self.schema.domain\n        ndim = dom.ndim\n        idx = _index_as_tuple(idx)\n\n        ranges = list()\n        for i,sel in enumerate(idx):\n            subranges = sel_to_subranges(sel)\n            ranges.append(subranges)\n\n        # extend the list to ndim\n        if len(ranges) < ndim:\n            ranges.extend([ tuple() for _ in range(ndim-len(ranges))])\n\n        rval = tuple(ranges)\n        return rval\n\n    def __getitem__(self, idx):\n        # implements multi-range / outer / orthogonal indexing\n        ranges = self.getitem_ranges(idx)\n\n        dom = self.schema.domain\n        attr_names = tuple(self.schema.attr(i).name for i in range(self.schema.nattr))\n\n        coords = None\n        if self.query is not None:\n            # if we are called via Query object, then we need to respect Query semantics\n            attr_names = tuple(self.query.attrs) if self.query.attrs else attr_names # query.attrs might be None -> all\n            coords = self.query.coords\n\n        # TODO order\n        result_dict = multi_index(\n            self.array,\n            attr_names,\n            ranges,\n            coords=coords\n        )\n\n        if self.schema.sparse:\n            return result_dict\n        else:\n            result_shape = mr_dense_result_shape(ranges, self.schema.shape)\n            for arr in result_dict.values():\n                # TODO check/test layout\n                arr.shape = result_shape\n            return result_dict", "path": "tiledb/multirange_indexing.py"}], "after_files": [{"content": "import tiledb\nfrom tiledb import Array, ArraySchema\nimport os, numpy as np\nimport sys, weakref\n\ntry:\n    from tiledb.libtiledb import multi_index\nexcept:\n    from tiledb.indexing import multi_index\n\n\ndef mr_dense_result_shape(ranges, base_shape = None):\n    # assumptions: len(ranges) matches number of dims\n    if base_shape is not None:\n        assert len(ranges) == len(base_shape), \"internal error: mismatched shapes\"\n\n    new_shape = list()\n    for i,rr in enumerate(ranges):\n        if rr != ():\n            m = list(map(lambda y: abs(y[1] - y[0]) + 1, rr))\n            new_shape.append(np.sum(m))\n        else:\n            if base_shape is None:\n                raise ValueError(\"Missing required base_shape for whole-dimension slices\")\n            # empty range covers dimension\n            new_shape.append(base_shape[i])\n\n    return tuple(new_shape)\n\ndef mr_dense_result_numel(ranges):\n    return np.prod(mr_dense_result_shape(ranges))\n\ndef sel_to_subranges(dim_sel):\n    subranges = list()\n    for range in dim_sel:\n        if np.isscalar(range):\n            subranges.append( (range, range) )\n        elif isinstance(range, slice):\n            if range.step is not None:\n                raise ValueError(\"Stepped slice ranges are not supported\")\n            elif range.start is None and range.stop is None:\n                # ':' full slice\n                pass\n            else:\n                subranges.append( (range.start, range.stop) )\n        elif isinstance(range, tuple):\n            subranges.extend((range,))\n        elif isinstance(range, list):\n            for el in range:\n                subranges.append( (el, el) )\n        else:\n            raise TypeError(\"Unsupported selection \")\n\n    return tuple(subranges)\n\n\nclass MultiRangeIndexer(object):\n    \"\"\"\n    Implements multi-range / outer / orthogonal indexing.\n\n    \"\"\"\n    # for cython\n    # comment out for Python 2 :/\n    #array: Array\n    #schema: ArraySchema\n    #def __init__(self, array: Array, query = None):\n\n    def __init__(self, array, query = None):\n        if not issubclass(type(array), tiledb.Array):\n            raise ValueError(\"Internal error: MultiRangeIndexer expected tiledb.Array\")\n        self.array_ref = weakref.ref(array)\n        self.schema = array.schema\n        self.query = query\n\n    @property\n    def array(self):\n        assert self.array_ref() is not None, \\\n            \"Internal error: invariant violation (indexing call w/ dead array_ref)\"\n        return self.array_ref()\n\n    @classmethod\n    def __test_init__(cls, array):\n        \"\"\"\n        Internal helper method for testing getitem range calculation.\n        :param array:\n        :return:\n        \"\"\"\n        m = cls.__new__(cls)\n        m.array_ref = weakref.ref(array)\n        m.schema = array.schema\n        m.query = None\n        return m\n\n    def getitem_ranges(self, idx):\n        dom = self.schema.domain\n        ndim = dom.ndim\n\n        if isinstance(idx, tuple):\n            idx = list(idx)\n        else:\n            idx = [idx]\n\n        ranges = list()\n        for i,sel in enumerate(idx):\n            if not isinstance(sel, list):\n                sel = [sel]\n            subranges = sel_to_subranges(sel)\n            ranges.append(subranges)\n\n        # extend the list to ndim\n        if len(ranges) < ndim:\n            ranges.extend([ tuple() for _ in range(ndim-len(ranges))])\n\n        rval = tuple(ranges)\n        return rval\n\n    def __getitem__(self, idx):\n        # implements multi-range / outer / orthogonal indexing\n        ranges = self.getitem_ranges(idx)\n\n        dom = self.schema.domain\n        attr_names = tuple(self.schema.attr(i).name for i in range(self.schema.nattr))\n\n        coords = None\n        if self.query is not None:\n            # if we are called via Query object, then we need to respect Query semantics\n            attr_names = tuple(self.query.attrs) if self.query.attrs else attr_names # query.attrs might be None -> all\n            coords = self.query.coords\n\n        # TODO order\n        result_dict = multi_index(\n            self.array,\n            attr_names,\n            ranges,\n            coords=coords\n        )\n\n        if self.schema.sparse:\n            return result_dict\n        else:\n            result_shape = mr_dense_result_shape(ranges, self.schema.shape)\n            for arr in result_dict.values():\n                # TODO check/test layout\n                arr.shape = result_shape\n            return result_dict", "path": "tiledb/multirange_indexing.py"}]}