Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@ Write the date in place of the "Unreleased" in the case a new version is release
- Tests and examples that use example config files; specifically an external
NeXus file used as an example of the structure is generated dynamically at
test time now.
- Type hint for `readable_storage` parameter for `SimpleTiledServer` indicated it
should be a string or `Path`, but it actually was required to be a list of strings
or list of `Paths`. This has been fixed.
- Missing docstring for `readable_storage` parameter added.
- Missing `properties` field in the `put_data_source` method on the adapter.

### Changed

- The `start_in_thread` method of `Subscription` now waits until the WebSocket
connection is established before returning.
- Allow for passing a single string or `Path` to `SimpleTiledServer`'s `readable_storage`
parameter. Generally, when using `SimpleTiledServer` one usually just passes `/tmp` or
`tmp_path` in unit tests.
- Unit test that confirms that the `readable_storage` setting works as expected, with
it being passed as a string, `Path`, list of strings, or list of `Path`s.

## v0.2.7 (2026-02-27)

Expand Down
29 changes: 29 additions & 0 deletions tests/test_simple_server.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import asyncio
import itertools
import platform
from pathlib import Path

Expand All @@ -6,6 +8,7 @@
import pytest

from tiled.client import SERVERS, from_uri, simple
from tiled.client.register import register
from tiled.server import SimpleTiledServer


Expand Down Expand Up @@ -64,6 +67,32 @@ def test_persistent_data(tmp_path):
assert server1.directory == server2.directory == tmp_path


@pytest.mark.parametrize(
("as_list", "as_path"), list(itertools.product([True, False], [True, False]))
)
def test_readable_storage(tmp_path, as_list, as_path):
"Run server with a user-specified readable storage location."
readable_storage = [tmp_path / "readable"] if as_list else tmp_path / "readable"
if as_path:
readable_storage = (
[Path(p) for p in readable_storage]
if isinstance(readable_storage, list)
else Path(readable_storage)
)
with SimpleTiledServer(
directory=tmp_path / "default", readable_storage=readable_storage
) as server:
client = from_uri(server.uri)
(tmp_path / "readable").mkdir(parents=True, exist_ok=True)
import h5py
import numpy

with h5py.File(tmp_path / "readable" / "data.h5", "w") as f:
f["x"] = numpy.array([1, 2, 3])
asyncio.run(register(client, tmp_path / "readable"))
assert (client["data"]["x"].read() == [1, 2, 3]).all()


def test_cleanup(tmp_path):
if platform.system() == "Windows":
# Windows cannot delete the logfiles because the global Python
Expand Down
12 changes: 11 additions & 1 deletion tiled/server/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class SimpleTiledServer:
port : Optional[int]
Port the server will listen on. By default, a random free high port
is allocated by the operating system.
readable_storage : Optional[Union[str, pathlib.Path, list[str], list[pathlib.Path]]]
If provided, the server will be able to read from these storage locations, in addition
to the default storage location defined by `directory`.

Examples
--------
Expand All @@ -83,7 +86,9 @@ def __init__(
directory: Optional[Union[str, pathlib.Path]] = None,
api_key: Optional[str] = None,
port: int = 0,
readable_storage: Optional[Union[str, pathlib.Path]] = None,
readable_storage: Optional[
Union[str, pathlib.Path, list[Union[str, pathlib.Path]]]
] = None,
):
# Delay import to avoid circular import.
from ..catalog import from_uri as catalog_from_uri
Expand Down Expand Up @@ -116,6 +121,11 @@ def __init__(
del log_config["handlers"]["default"]["stream"]
log_config["handlers"]["default"]["filename"] = str(directory / "error.log")

# Catalog from uri wants readable storage to be a list,
# but we want to allow users to pass in a single path for convenience.
if readable_storage is not None and not isinstance(readable_storage, list):
readable_storage = [readable_storage]

self.catalog = catalog_from_uri(
directory / "catalog.db",
writable_storage=[directory / "data", storage_uri],
Expand Down
Loading