diff --git a/doc/architecture.md b/doc/architecture.md index 4db4d7a..a97f755 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -398,7 +398,7 @@ param.edit_async(...)` for parameter dialogs. - **`dlw_applications.py`** — generic application host. It prepares typed recipe slots, assesses recipe readiness on candidate objects and edited - bindings (through the shim's `datalab.recipe_binding`, shared with Desktop), + bindings (through the shim's `datalab.plugins.recipe_binding`, shared with Desktop), bridges guidata parameter schemas, opens packaged/generated examples, and commits recipe objects, results, diagnostics, and provenance transactionally. It contains no Camera- or Pulse-specific paths. diff --git a/doc/plugins.md b/doc/plugins.md index c4ac6a1..74671b2 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -130,18 +130,18 @@ and may implement `materialize_example()`. Recipe slots carry signal/image type, `RecipeOutcome`; DataLab-Web commits objects, anchored scalar results, diagnostics, and provenance transactionally. -The Applications dialog presents each recipe as a method card: expected inputs, a readiness status re-assessed when the selection changes, a _Run on selection…_ button, and _Try with this example_ for each example designed for it. Examples without a recipe are listed as datasets. Readiness and binding checks use the same `datalab.recipe_binding` module as DataLab Desktop, copied into the host shim, so both hosts accept the same selections. +The Applications dialog presents each recipe as a method card: expected inputs, a readiness status re-assessed when the selection changes, a _Run on selection…_ button, and _Try with this example_ for each example designed for it. Examples without a recipe are listed as datasets. Readiness and binding checks use the same `datalab.plugins.recipe_binding` module as DataLab Desktop, copied into the host shim, so both hosts accept the same selections. ### Tools and instruments -Application plugins may also declare `TOOLS`, a tuple of `datalab.plugin_tools.PluginTool` values, as on DataLab Desktop. The Applications dialog lists them in a **Tools** section, and the _Plugins_ menu lists them in the plugin's submenu, after its methods and examples. `object_type` places a tool in the signal or image menu (both when `None`), and `selection` (`none`, `exactly_one`, `at_least_one`, `at_least_two`) enables it only when enough objects of that type are selected; otherwise the dialog tells which objects to select. +Application plugins may also declare `TOOLS`, a tuple of `datalab.plugins.tools.PluginTool` values, as on DataLab Desktop. The Applications dialog lists them in a **Tools** section, and the _Plugins_ menu lists them in the plugin's submenu, after its methods and examples. `object_type` places a tool in the signal or image menu (both when `None`), and `selection` (`none`, `exactly_one`, `at_least_one`, `at_least_two`) enables it only when enough objects of that type are selected; otherwise the dialog tells which objects to select. A tool is opened in one of two ways: - **Launcher** (`launcher="method_name"`): DataLab calls the plugin method, and awaits it when it is a coroutine, so it can open dialogs with `edit_async()`. `self.get_selected_objects()` returns the objects selected when the tool was launched; objects the tool adds are refreshed and selected afterwards. -- **Instrument** (`instrument="method_name"`): the method returns a `datalab.plugin_instruments.PluginInstrument`, which holds its settings in a guidata `DataSet` and implements `preview()` (an `InstrumentFrame`: signals drawn together, or one image, with a summary and an optional fixed range) and `acquire()` (an `InstrumentAcquisition`: a group title and the acquired objects). DataLab draws the window itself: a live view on the left, the settings form on the right, a _Live_ toggle refreshing the view every `live_interval_ms`, and an _Acquire_ button adding each acquisition to a new group. The instrument instance lives as long as the plugin is registered, so settings are kept between openings. The plugin writes no user interface code, so the same instrument runs on DataLab Desktop and DataLab-Web. +- **Instrument** (`instrument="method_name"`): the method returns a `datalab.plugins.instruments.PluginInstrument`, which holds its settings in a guidata `DataSet` and implements `preview()` (an `InstrumentFrame`: signals drawn together, or one image, with a summary and an optional fixed range) and `acquire()` (an `InstrumentAcquisition`: a group title and the acquired objects). DataLab draws the window itself: a live view on the left, the settings form on the right, a _Live_ toggle refreshing the view every `live_interval_ms`, and an _Acquire_ button adding each acquisition to a new group. The instrument instance lives as long as the plugin is registered, so settings are kept between openings. The plugin writes no user interface code, so the same instrument runs on DataLab Desktop and DataLab-Web. -`datalab.plugin_tools`, `datalab.plugin_instruments` and `datalab.plugin_resources` are copied from DataLab Desktop into the host shim, like `datalab.recipe_binding`. +`datalab.plugins.tools`, `datalab.plugins.instruments` and `datalab.plugins.resources` are copied from DataLab Desktop into the host shim, like `datalab.plugins.recipe_binding`. Version 1 of the installer deliberately accepts only local `*-none-any` pure-Python wheels compatible with Pyodide's Python version. Native payloads, diff --git a/src/runtime/builtin_wheels/datalab_camera_characterization-0.2.0-py3-none-any.whl b/src/runtime/builtin_wheels/datalab_camera_characterization-0.2.0-py3-none-any.whl index 19f909e..38cf22b 100644 Binary files a/src/runtime/builtin_wheels/datalab_camera_characterization-0.2.0-py3-none-any.whl and b/src/runtime/builtin_wheels/datalab_camera_characterization-0.2.0-py3-none-any.whl differ diff --git a/src/runtime/builtin_wheels/datalab_pulse_characterization-0.2.0-py3-none-any.whl b/src/runtime/builtin_wheels/datalab_pulse_characterization-0.2.0-py3-none-any.whl index 353d9c7..a5e39b5 100644 Binary files a/src/runtime/builtin_wheels/datalab_pulse_characterization-0.2.0-py3-none-any.whl and b/src/runtime/builtin_wheels/datalab_pulse_characterization-0.2.0-py3-none-any.whl differ diff --git a/src/runtime/bundledPlugins.ts b/src/runtime/bundledPlugins.ts index 3e362f8..a1d66c6 100644 --- a/src/runtime/bundledPlugins.ts +++ b/src/runtime/bundledPlugins.ts @@ -18,8 +18,8 @@ export const BUNDLED_PLUGIN_WHEELS: readonly BundledPluginWheel[] = version: "0.2.0", filename: "datalab_camera_characterization-0.2.0-py3-none-any.whl", sha256: - "e89635561bd7b8eb10dd54f4f21ad4358d0b3562c7d016b8066ef1239b56a5c3", - sizeBytes: 332_561, + "d5e483caf43418c5182ac717ce724ba95b973e4aea835be6cf8b0af20716b81a", + sizeBytes: 332_591, url: cameraWheelUrl, }), Object.freeze({ @@ -27,8 +27,8 @@ export const BUNDLED_PLUGIN_WHEELS: readonly BundledPluginWheel[] = version: "0.2.0", filename: "datalab_pulse_characterization-0.2.0-py3-none-any.whl", sha256: - "317c1c1bb4dc32d270fa6f6586e830c0cea41dd3695bc70b42f4628d54466919", - sizeBytes: 82_275, + "d8797b52a5172a8c426ab72c8772001070760ee71d5a755f4ccdcd9bbc19c83e", + sizeBytes: 82_323, url: pulseWheelUrl, }), ]); diff --git a/src/runtime/dlplugins/datalab/plugins/__init__.py b/src/runtime/dlplugins/datalab/plugins/__init__.py new file mode 100644 index 0000000..1b2ac70 --- /dev/null +++ b/src/runtime/dlplugins/datalab/plugins/__init__.py @@ -0,0 +1,64 @@ +# Copyright (c) DataLab Platform Developers, BSD 3-Clause License +# See LICENSE file for details +""" +Portable copy of the :mod:`datalab.plugins` package for DataLab-Web. + +Like DataLab Desktop, the plugin host lives in :mod:`datalab.plugins.base` and its +public names are re-exported here, while the plugin contracts (recipes, tools, +examples...) are submodules of this package. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from datalab.plugins.base import ( + PLUGINS_DEFAULT_PATH, + ClassicsImageFormat, + FailedPluginInfo, + FormatInfo, + ImageFormatBase, + PluginBase, + PluginBaseMeta, + PluginCapability, + PluginInfo, + PluginRegistry, + SignalFormatBase, + discover_plugins, + format_tool_requirement, + reload_plugin_modules, + ) + +__all__ = [ + "PLUGINS_DEFAULT_PATH", + "ClassicsImageFormat", + "FailedPluginInfo", + "FormatInfo", + "ImageFormatBase", + "PluginBase", + "PluginBaseMeta", + "PluginCapability", + "PluginInfo", + "PluginRegistry", + "SignalFormatBase", + "discover_plugins", + "format_tool_requirement", + "reload_plugin_modules", +] + + +# Importing the plugin host lazily keeps contract submodules lightweight +def __getattr__(name: str) -> object: + """Return a public name of the plugin host, importing it on first access.""" + if name in __all__: + # pylint: disable=import-outside-toplevel + from datalab.plugins import base + + return getattr(base, name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +def __dir__() -> list[str]: + """Return module attributes, including lazily imported public names.""" + return sorted({*globals(), *__all__}) diff --git a/src/runtime/dlplugins/datalab/plugins.py b/src/runtime/dlplugins/datalab/plugins/base.py similarity index 98% rename from src/runtime/dlplugins/datalab/plugins.py rename to src/runtime/dlplugins/datalab/plugins/base.py index ec38415..2c0c621 100644 --- a/src/runtime/dlplugins/datalab/plugins.py +++ b/src/runtime/dlplugins/datalab/plugins/base.py @@ -1,13 +1,13 @@ # Copyright (c) DataLab Platform Developers, BSD 3-Clause License # See LICENSE file for details """ -Portable copy of :mod:`datalab.plugins` for DataLab-Web. +Portable copy of :mod:`datalab.plugins.base` for DataLab-Web. This is a Qt-free re-implementation of the Qt plugin core. It keeps the public surface (:class:`PluginInfo`, :class:`PluginBase`, :class:`PluginRegistry`, :class:`FailedPluginInfo`, :func:`discover_plugins`) bit-for-bit compatible with -``c:/Dev/DataLab/datalab/plugins.py`` so that an unmodified Qt plugin +``c:/Dev/DataLab/datalab/plugins/base.py`` so that an unmodified Qt plugin that imports ``from datalab.plugins import PluginBase, PluginInfo`` and that does not pull in :mod:`qtpy` itself loads as-is in the browser. @@ -52,10 +52,10 @@ from datalab.config import MOD_NAME, Conf, _ from datalab.control.proxy import LocalProxy from datalab.env import execenv -from datalab.plugin_examples import PluginExample, PluginExampleData -from datalab.plugin_instruments import PluginInstrument -from datalab.plugin_tools import PluginTool, ToolSelection, tool_accepts_selection -from datalab.recipes import RecipeDescriptor +from datalab.plugins.examples import PluginExample, PluginExampleData +from datalab.plugins.instruments import PluginInstrument +from datalab.plugins.recipes import RecipeDescriptor +from datalab.plugins.tools import PluginTool, ToolSelection, tool_accepts_selection if TYPE_CHECKING: from sigima.objects import ImageObj, NewImageParam, NewSignalParam, SignalObj diff --git a/src/runtime/dlplugins/datalab/plugin_examples.py b/src/runtime/dlplugins/datalab/plugins/examples.py similarity index 100% rename from src/runtime/dlplugins/datalab/plugin_examples.py rename to src/runtime/dlplugins/datalab/plugins/examples.py diff --git a/src/runtime/dlplugins/datalab/plugin_instruments.py b/src/runtime/dlplugins/datalab/plugins/instruments.py similarity index 100% rename from src/runtime/dlplugins/datalab/plugin_instruments.py rename to src/runtime/dlplugins/datalab/plugins/instruments.py diff --git a/src/runtime/dlplugins/datalab/recipe_binding.py b/src/runtime/dlplugins/datalab/plugins/recipe_binding.py similarity index 99% rename from src/runtime/dlplugins/datalab/recipe_binding.py rename to src/runtime/dlplugins/datalab/plugins/recipe_binding.py index 4d0f369..d85ea15 100644 --- a/src/runtime/dlplugins/datalab/recipe_binding.py +++ b/src/runtime/dlplugins/datalab/plugins/recipe_binding.py @@ -18,7 +18,7 @@ import guidata.dataset as gds from sigima.objects import ImageObj, SignalObj -from datalab.recipes import ( +from datalab.plugins.recipes import ( RecipeCardinality, RecipeDescriptor, RecipeDiagnostic, diff --git a/src/runtime/dlplugins/datalab/recipes.py b/src/runtime/dlplugins/datalab/plugins/recipes.py similarity index 100% rename from src/runtime/dlplugins/datalab/recipes.py rename to src/runtime/dlplugins/datalab/plugins/recipes.py diff --git a/src/runtime/dlplugins/datalab/plugin_resources.py b/src/runtime/dlplugins/datalab/plugins/resources.py similarity index 100% rename from src/runtime/dlplugins/datalab/plugin_resources.py rename to src/runtime/dlplugins/datalab/plugins/resources.py diff --git a/src/runtime/dlplugins/datalab/plugin_tools.py b/src/runtime/dlplugins/datalab/plugins/tools.py similarity index 95% rename from src/runtime/dlplugins/datalab/plugin_tools.py rename to src/runtime/dlplugins/datalab/plugins/tools.py index 718d7de..a939527 100644 --- a/src/runtime/dlplugins/datalab/plugin_tools.py +++ b/src/runtime/dlplugins/datalab/plugins/tools.py @@ -13,8 +13,8 @@ from sigima.objects import ImageObj, SignalObj -from datalab.plugin_resources import LOCAL_ID_PATTERN, split_package_resource -from datalab.recipes import RecipeObjectType +from datalab.plugins.recipes import RecipeObjectType +from datalab.plugins.resources import LOCAL_ID_PATTERN, split_package_resource __all__ = ["PluginTool", "ToolSelection", "tool_accepts_selection"] @@ -39,7 +39,7 @@ class PluginTool: A tool is opened either by a ``launcher`` method, called without arguments, or by an ``instrument`` method returning a - :class:`datalab.plugin_instruments.PluginInstrument`, which DataLab shows + :class:`datalab.plugins.instruments.PluginInstrument`, which DataLab shows in a window with a live view and the instrument settings. Args: diff --git a/src/runtime/dlw_applications.py b/src/runtime/dlw_applications.py index f95bc6d..6c0d8cf 100644 --- a/src/runtime/dlw_applications.py +++ b/src/runtime/dlw_applications.py @@ -16,8 +16,8 @@ import dlw_plugins import guidata.dataset as gds import sigima -from datalab.plugin_examples import PluginExampleData -from datalab.recipe_binding import ( +from datalab.plugins.examples import PluginExampleData +from datalab.plugins.recipe_binding import ( RecipeInputIssueCode, RecipeReadiness, assess_recipe_inputs, @@ -25,7 +25,7 @@ find_input_issues, is_compatible, ) -from datalab.recipes import ( +from datalab.plugins.recipes import ( RECIPE_RUN_RECORD_OPTION, RecipeCardinality, RecipeDescriptor, diff --git a/src/runtime/dlw_plugins.py b/src/runtime/dlw_plugins.py index f16ab82..b74b13d 100644 --- a/src/runtime/dlw_plugins.py +++ b/src/runtime/dlw_plugins.py @@ -10,7 +10,7 @@ * the directory discovery loop (``discover_plugins_in_dir(path)``); * the hot-reload sequence (``reload_plugins()``). -The Qt counterpart lives in ``datalab/plugins.py``; here we only need +The Qt counterpart lives in ``datalab/plugins/base.py``; here we only need the pieces that DataLab-Web actually uses, in a single self-contained module so the bootstrap stays focused on the object model. """ diff --git a/src/runtime/runtime.ts b/src/runtime/runtime.ts index 4cbb131..d961a03 100644 --- a/src/runtime/runtime.ts +++ b/src/runtime/runtime.ts @@ -1695,7 +1695,7 @@ await micropip.install([${JSON.stringify(SIGIMA_INSTALL_SPEC)}, ${JSON.stringify * Mirror the portable ``datalab.*`` shim into Pyodide's site-packages. * * Each entry in *sources* is keyed by the workspace-relative path - * (``./dlplugins/datalab/plugins.py``); we strip the leading + * (``./dlplugins/datalab/plugins/base.py``); we strip the leading * ``./dlplugins/`` and write to ``/home/pyodide/``. */ private static installShim( diff --git a/tests/e2e/user_plugin_wheel.spec.ts b/tests/e2e/user_plugin_wheel.spec.ts index aca4880..b9babf8 100644 --- a/tests/e2e/user_plugin_wheel.spec.ts +++ b/tests/e2e/user_plugin_wheel.spec.ts @@ -73,7 +73,7 @@ function buildPluginWheel(): Buffer { import numpy as np from datalab.plugins import PluginBase, PluginCapability, PluginInfo -from datalab.recipes import ( +from datalab.plugins.recipes import ( RecipeDescriptor, RecipeObjectOutput, RecipeOutcome, diff --git a/tests/python/test_applications.py b/tests/python/test_applications.py index 747773c..c0a38c9 100644 --- a/tests/python/test_applications.py +++ b/tests/python/test_applications.py @@ -11,15 +11,14 @@ import dlw_plugins import numpy as np import pytest -from datalab.plugin_examples import PluginExampleData -from datalab.plugin_instruments import ( +from datalab.plugins import PluginBase, PluginCapability, PluginInfo +from datalab.plugins.examples import PluginExampleData +from datalab.plugins.instruments import ( InstrumentAcquisition, InstrumentFrame, PluginInstrument, ) -from datalab.plugin_tools import PluginTool -from datalab.plugins import PluginBase, PluginCapability, PluginInfo -from datalab.recipes import ( +from datalab.plugins.recipes import ( RECIPE_RUN_RECORD_OPTION, RecipeCardinality, RecipeDescriptor, @@ -33,6 +32,7 @@ RecipeResultOutput, RecipeValidationError, ) +from datalab.plugins.tools import PluginTool from guidata import dataset as gds from sigima.objects import create_image, create_signal from sigima.objects.scalar import TableResult diff --git a/tests/python/test_camera_application.py b/tests/python/test_camera_application.py index 702044d..c4fb8eb 100644 --- a/tests/python/test_camera_application.py +++ b/tests/python/test_camera_application.py @@ -10,7 +10,7 @@ import dlw_applications import dlw_plugins import pytest -from datalab.recipes import RECIPE_RUN_RECORD_OPTION +from datalab.plugins.recipes import RECIPE_RUN_RECORD_OPTION REPO_ROOT = Path(__file__).resolve().parents[2] CAMERA_WHEEL = ( diff --git a/tests/python/test_pulse_application.py b/tests/python/test_pulse_application.py index fb490dc..00404b5 100644 --- a/tests/python/test_pulse_application.py +++ b/tests/python/test_pulse_application.py @@ -11,7 +11,7 @@ import dlw_applications import dlw_plugins import pytest -from datalab.recipes import RECIPE_RUN_RECORD_OPTION +from datalab.plugins.recipes import RECIPE_RUN_RECORD_OPTION REPO_ROOT = Path(__file__).resolve().parents[2] PULSE_WHEEL = (