Skip to content
Merged
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
120 changes: 120 additions & 0 deletions .github/scripts/minimal_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Smoke test for a MINIMAL installation of bluesky-tiled-plugins.

Run with plain python (not pytest) in an environment where only the package and
its declared runtime dependencies are installed -- no extras, no dev/test
dependency groups:

uv venv && uv pip install . && .venv/bin/python .github/scripts/minimal_smoke.py

It verifies that the declared runtime dependencies are sufficient: every
submodule imports, the client-side writing machinery works, and the optional
ragged dependencies are genuinely absent (so this script will fail by design
if run in a full dev/test environment).
"""

import importlib
import pkgutil
import traceback

import bluesky_tiled_plugins as btp

# Modules that intentionally require tiled[server] and are only imported in
# server deployments (e.g. mounted via the server config).
SERVER_ONLY = {"bluesky_tiled_plugins.routers.validator"}

failures = []
for mod in pkgutil.walk_packages(btp.__path__, btp.__name__ + "."):
if mod.name in SERVER_ONLY:
continue
try:
importlib.import_module(mod.name)
except Exception:
failures.append((mod.name, traceback.format_exc()))

for name, tb in failures:
print(f"FAILED IMPORT: {name}\n{tb}")
if failures:
raise SystemExit(f"{len(failures)} module(s) failed to import; see above.")

# The public API is usable.
from bluesky_tiled_plugins import TiledInserter, TiledWriter # noqa: E402, F401
from bluesky_tiled_plugins.writing.consolidators import ( # noqa: E402
consolidator_factory,
)
from bluesky_tiled_plugins.writing.tiled_writer import RunNormalizer # noqa: E402

# The document-processing machinery works without a server.
received = []
normalizer = RunNormalizer()
normalizer.subscribe(lambda name, doc: received.append(name))
normalizer("start", {"uid": "u1", "time": 0.0})
normalizer(
"descriptor",
{
"uid": "d1",
"run_start": "u1",
"time": 0.0,
"name": "primary",
"data_keys": {"x": {"source": "s", "dtype": "number", "shape": []}},
"object_keys": {"det": ["x"]},
"configuration": {},
"hints": {},
},
)
normalizer(
"event",
{
"uid": "e1",
"descriptor": "d1",
"time": 1.0,
"seq_num": 1,
"data": {"x": 1.0},
"timestamps": {"x": 1.0},
"filled": {},
},
)
normalizer(
"stop",
{
"uid": "s1",
"run_start": "u1",
"time": 2.0,
"exit_status": "success",
"reason": "",
"num_events": {"primary": 1},
},
)
assert "start" in received and "stop" in received, received

consolidator = consolidator_factory(
{
"data_key": "img",
"uri": "file:///x",
"mimetype": "multipart/related;type=image/tiff",
"parameters": {"chunk_shape": (1, 5, 7), "template": "f_{:06d}.tiff"},
},
{
"data_keys": {
"img": {"shape": [3, 5, 7], "dtype": "array", "dtype_numpy": "|u1"}
}
},
)
consolidator.consume_stream_datum(
{"indices": {"start": 0, "stop": 1}, "seq_nums": {"start": 1, "stop": 2}}
)
assert consolidator.shape and consolidator.assets
assert consolidator.structure() is not None

# The optional ragged dependencies must NOT be present in a minimal install;
# this guards against them sneaking back in through a transitive dependency
# (install bluesky-tiled-plugins[ragged] to get them).
try:
import tiled.structures.ragged # noqa: F401
except ImportError:
pass
else:
raise SystemExit(
"tiled.structures.ragged is importable: the environment is not minimal."
)

print("Minimal installation smoke test: OK")
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

# The jobs only check out the repo and run tests; the codecov upload uses its
# own CODECOV_TOKEN secret, not the GITHUB_TOKEN.
permissions:
contents: read

env:
# Many color libraries just need this variable to be set to any value.
# Set it to 3 to support 8-bit color graphics (256 colors per channel)
Expand Down Expand Up @@ -90,6 +95,39 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}

# Verify that the declared runtime dependencies are sufficient on their own:
# install the package with no extras and no dev/test dependency groups (so
# e.g. tiled[server] in the test group does not mask a missing runtime
# dependency) and smoke test it. See .github/scripts/minimal_smoke.py.
minimal-install:
name: Minimal install / Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
needs: [pre-commit]
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.13"]

steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- uses: astral-sh/setup-uv@v7

- name: Install package without extras or dependency groups
run: |
uv venv --python ${{ matrix.python-version }}
uv pip install .

- name: Smoke test the minimal installation
run: .venv/bin/python .github/scripts/minimal_smoke.py

# Windows and macOS jobs run without Mongo/Redis. Tests gated on
# TEST_MONGO_URI / TEST_REDIS_URI skip themselves automatically.
other-checks:
Expand Down
21 changes: 19 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,26 @@ dependencies = [
"dask",
"event-model",
"mongoquery",
"ndindex", # imported by tiled.ndslice
"numpy",
"pandas",
"pyarrow",
"pytz",
"tiled[client] >=0.2.15",
"tiled[minimal-client] >=0.2.19",
"tzlocal",
"xarray",
]

[project.optional-dependencies]
# Writing ragged (variable-length) array data; imported lazily by TiledWriter.
# Pins match the tiled[client] extra.
ragged = [
"awkward >=2.4.3",
"ragged",
]
# All optional features.
all = [
"bluesky-tiled-plugins[ragged]",
]


Expand Down Expand Up @@ -71,7 +88,7 @@ test = [
"redis-json-dict",
"suitcase-mongo",
"tifffile",
"tiled[server] >=0.2.15",
"tiled[server] >=0.2.19",
]
dev = [{ include-group = "test" }]
docs = [
Expand Down
9 changes: 8 additions & 1 deletion src/bluesky_tiled_plugins/writing/tiled_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,14 @@ def _write_internal_data(

# 2. Write internal ragged array data, if any; remove it from the tabular data
for key in self._int_ragged_array_keys[desc_name]:
from tiled.structures.ragged import make_ragged_array
try:
from tiled.structures.ragged import make_ragged_array
except ImportError as err:
raise ImportError(
"Writing ragged (variable-length) array data requires optional "
"dependencies. Install them with: "
"pip install bluesky-tiled-plugins[ragged]"
) from err

arr_lst = [row.pop(key) for row in data_cache if key in row]
shape = (len(arr_lst), *self.data_keys[key].get("shape", ()))
Expand Down
Loading