Skip to content

Commit 28ba02c

Browse files
committed
Convert async custom message handlers to sync
1 parent d1bf74e commit 28ba02c

2 files changed

Lines changed: 83 additions & 23 deletions

File tree

src/vitessce/sync_store.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copied from zarr-python
2+
# Reference: https://github.com/zarr-developers/zarr-python/blob/fe229107f9915f05817f7a664d3550695ff9ca44/src/zarr/testing/stateful.py#L438
3+
4+
import builtins
5+
from typing import Any
6+
import zarr
7+
from zarr.abc.store import Store
8+
from zarr.core.buffer import Buffer, BufferPrototype
9+
10+
class SyncStoreWrapper(zarr.core.sync.SyncMixin):
11+
def __init__(self, store: Store) -> None:
12+
"""Synchronous Store wrapper
13+
14+
This class holds synchronous methods that map to async methods of Store classes.
15+
The synchronous wrapper is needed because hypothesis' stateful testing infra does
16+
not support asyncio so we redefine sync versions of the Store API.
17+
https://github.com/HypothesisWorks/hypothesis/issues/3712#issuecomment-1668999041
18+
"""
19+
self.store = store
20+
21+
@property
22+
def read_only(self) -> bool:
23+
return self.store.read_only
24+
25+
def set(self, key: str, data_buffer: Buffer) -> None:
26+
return self._sync(self.store.set(key, data_buffer))
27+
28+
def list(self) -> builtins.list[str]:
29+
return self._sync_iter(self.store.list())
30+
31+
def get(self, key: str, prototype: BufferPrototype, **kwargs) -> Buffer | None:
32+
return self._sync(self.store.get(key, prototype=prototype, **kwargs))
33+
34+
def get_partial_values(
35+
self, key_ranges: builtins.list[Any], prototype: BufferPrototype
36+
) -> builtins.list[Buffer | None]:
37+
return self._sync(self.store.get_partial_values(prototype=prototype, key_ranges=key_ranges))
38+
39+
def delete(self, path: str) -> None:
40+
return self._sync(self.store.delete(path))
41+
42+
def is_empty(self, prefix: str) -> bool:
43+
return self._sync(self.store.is_empty(prefix=prefix))
44+
45+
def clear(self) -> None:
46+
return self._sync(self.store.clear())
47+
48+
def exists(self, key: str) -> bool:
49+
return self._sync(self.store.exists(key))
50+
51+
def list_dir(self, prefix: str) -> None:
52+
raise NotImplementedError
53+
54+
def list_prefix(self, prefix: str) -> None:
55+
raise NotImplementedError
56+
57+
@property
58+
def supports_listing(self) -> bool:
59+
return self.store.supports_listing
60+
61+
@property
62+
def supports_writes(self) -> bool:
63+
return self.store.supports_writes
64+
65+
@property
66+
def supports_deletes(self) -> bool:
67+
return self.store.supports_deletes

src/vitessce/widget.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import anywidget
88
from traitlets import Unicode, Dict, List, Int, Bool
99

10-
import asyncio
1110
from zarr.abc.store import RangeByteRequest, SuffixByteRequest
1211
from zarr.core.buffer.core import default_buffer_prototype
12+
from .sync_store import SyncStoreWrapper
1313

1414

1515
MAX_PORT_TRIES = 1000
@@ -912,11 +912,11 @@ def close(self):
912912
super().close()
913913

914914
# @anywidget.experimental.command
915-
async def _zarr_get(self, params, buffers):
915+
def _zarr_get(self, params, buffers):
916916
[store_url, key] = params
917-
store = self._stores[store_url]
917+
store = SyncStoreWrapper(self._stores[store_url])
918918
try:
919-
result = await store.get(key.lstrip("/"), prototype=default_buffer_prototype())
919+
result = store.get(key.lstrip("/"), prototype=default_buffer_prototype())
920920
if result is None:
921921
buffers = []
922922
else:
@@ -927,9 +927,9 @@ async def _zarr_get(self, params, buffers):
927927
return {"success": len(buffers) == 1}, buffers
928928

929929
# @anywidget.experimental.command
930-
async def _zarr_get_range(self, params, buffers):
930+
def _zarr_get_range(self, params, buffers):
931931
[store_url, key, range_query] = params
932-
store = self._stores[store_url]
932+
store = SyncStoreWrapper(self._stores[store_url])
933933
try:
934934
range_param = None
935935
# Reference: https://github.com/manzt/zarrita.js/blob/f63a2521e2b46b22aa26af4146822e4d827dff83/packages/%40zarrita-storage/src/types.ts#L3
@@ -943,7 +943,7 @@ async def _zarr_get_range(self, params, buffers):
943943
else:
944944
raise ValueError(f"Invalid range query: {range_query}. Must contain either 'suffixLength' or both 'offset' and 'length'.")
945945

946-
result = await store.get(key, byte_range=range_param, prototype=default_buffer_prototype())
946+
result = store.get(key, byte_range=range_param, prototype=default_buffer_prototype())
947947
if result is None:
948948
buffers = []
949949
else:
@@ -954,16 +954,16 @@ async def _zarr_get_range(self, params, buffers):
954954
return {"success": len(buffers) == 1}, buffers
955955

956956
# @anywidget.experimental.command
957-
async def _zarr_get_multi(self, params_arr, buffers):
957+
def _zarr_get_multi(self, params_arr, buffers):
958958
# This variant of _zarr_get and _zarr_get_range supports batching.
959959
result_dicts = []
960960
result_buffers = []
961961
for params in params_arr:
962962
result_dict, result_buffer_arr = {}, []
963963
if len(params) == 2:
964-
result_dict, result_buffer_arr = await self._zarr_get(params, buffers)
964+
result_dict, result_buffer_arr = self._zarr_get(params, buffers)
965965
elif len(params) == 3:
966-
result_dict, result_buffer_arr = await self._zarr_get_range(params, buffers)
966+
result_dict, result_buffer_arr = self._zarr_get_range(params, buffers)
967967
else:
968968
raise ValueError("Expected params to have len 2 or 3 in _zarr_get_multi")
969969
if result_dict["success"] and len(result_buffer_arr) == 1:
@@ -986,28 +986,21 @@ def _handle_msg(self, msg: dict) -> None:
986986
if content.get("kind") != "anywidget-command":
987987
super()._handle_msg(msg)
988988
return
989-
try:
990-
loop = asyncio.get_event_loop()
991-
except RuntimeError:
992-
return
993-
if loop.is_running():
994-
loop.create_task(self._dispatch_command(content, buffers))
995-
else:
996-
loop.run_until_complete(self._dispatch_command(content, buffers))
989+
self._dispatch_command(content, buffers)
997990

998-
async def _dispatch_command(self, msg: dict, buffers: list[bytes]) -> None:
991+
def _dispatch_command(self, msg: dict, buffers: list[bytes]) -> None:
999992
name = msg.get("name")
1000993
params = msg.get("msg")
1001994
msg_id = msg.get("id")
1002995
try:
1003996
if name == "_zarr_get":
1004-
response, result_buffers = await self._zarr_get(params, buffers)
997+
response, result_buffers = self._zarr_get(params, buffers)
1005998
elif name == "_zarr_get_range":
1006-
response, result_buffers = await self._zarr_get_range(params, buffers)
999+
response, result_buffers = self._zarr_get_range(params, buffers)
10071000
elif name == "_zarr_get_multi":
1008-
response, result_buffers = await self._zarr_get_multi(params, buffers)
1001+
response, result_buffers = self._zarr_get_multi(params, buffers)
10091002
elif name == "_plugin_command":
1010-
response, result_buffers = await self._plugin_command(params, buffers)
1003+
response, result_buffers = self._plugin_command(params, buffers)
10111004
else:
10121005
return
10131006
except Exception as exc: # noqa: BLE001

0 commit comments

Comments
 (0)