Shared Desktop plugins target Python 3.10 or later. Web plugins still target the Python 3.12 interpreter provided by Pyodide; DataLab-Web's CPython test minimum remains Python 3.11.
DataLab-Web supports portable DataLab plugin classes inside Pyodide. A plugin may be loaded as a consented single Python source file or installed as a managed pure-Python wheel. Plugin code shares the main Python interpreter and is not sandboxed. It must not block the JavaScript event loop.
For action-oriented source plugins, any path that opens a parameter dialog must
be async and use await param.edit_async(self.main) instead of synchronous
param.edit(self.main). Application wheels should declare headless recipes
and examples; the generic Applications host owns their dialogs and commits.
The good news: the same plugin source can be used in both DataLab Qt
and DataLab-Web. DataSet.edit_async() is implemented in upstream
guidata; on Qt it simply delegates to edit().
# my_plugin.py
from __future__ import annotations
from datalab.config import _
from datalab.plugins import PluginBase, PluginInfo
class MyPlugin(PluginBase):
"""Minimal DataLab-Web plugin example."""
PLUGIN_INFO = PluginInfo(
id="org.example.my-plugin",
name=_("My plugin"),
version="0.1.0",
description=_("Adds a few useful actions"),
)
async def hello_world(self) -> None:
# Use the proxy to manipulate the workspace.
# `add_object`, `calc`, `get_object*`, `call_method` are all
# available — see ``datalab.control.proxy.LocalProxy``.
sig = ... # build a SignalObj or ImageObj
self.proxy.add_object(sig)
def create_actions(self) -> None:
sah = self.signalpanel.acthandler
with sah.new_menu(_("My plugin")):
sah.new_action(
_("Hello world"),
triggered=self.hello_world,
select_condition="always",
)PluginBase exposes the same surface as the desktop counterpart:
| Attribute / method | Purpose |
|---|---|
self.main |
Bridge to the React main window (parent for dialogs) |
self.signalpanel, self.imagepanel |
Panels with processor, acthandler, get_newparam_from_current |
self.proxy |
LocalProxy-compatible API: add_object, add_group, calc, get_object*, call_method |
await self.show_info_async(msg) |
Async info popup (show_warning_async / show_error_async for the other kinds) |
await self.ask_yesno_async(msg, cancelable=…) |
Async yes/no(/cancel) prompt |
await self.edit_new_signal_parameters_async(…) |
Async variant of the desktop helper |
await self.edit_new_image_parameters_async(…) |
Async variant of the desktop helper |
The synchronous helpers (self.show_info / show_warning / show_error /
ask_yesno) exist for desktop source compatibility but raise
BrowserNotSupportedError in Pyodide — use the _async variants above.
- Synchronous dialogs raise
BrowserNotSupportedError.param.edit(self.main),BaseProxy.calc(...)blocking calls and any similar synchronous prompt are unavailable. Always use the_asynccounterparts. - No filesystem access beyond Pyodide's in-memory FS. Plugins that read local files on the desktop should use the file picker exposed by the host UI instead.
- No worker / process isolation. All plugin code runs in the same
Python interpreter as Sigima. Long-running computations will freeze
the UI; consider chunking via
asyncio.sleep(0)to yield.
- Python source: open Plugins → Manage plugins…, click
Load Python file… and select a
.pyfile. The first time a plugin is loaded you will be prompted to confirm execution. The decision is remembered (per source SHA-256) inlocalStorage. - Local wheel: click Install wheel… and select a
.whlfile. DataLab-Web inspects the archive before consent and before importing plugin code. The review shows its SHA-256, distribution, version, Python requirement, tags, Web entry points, and dependency compatibility. Accepted artifacts and activation preferences are persisted under the dedicateddlw-plugin-wheelsOPFS subtree. This command is disabled when persistent OPFS storage is unavailable. - Bundled examples: the Plugins → Manage plugins… dialog lists the
example plugins shipped under the repo-root
plugins/examples/folder (mirroring DataLab desktop). Click Load next to one to try it; the same consent gate applies. These are not auto-loaded. - Bundled built-ins: drop a file into
src/runtime/builtin_plugins/; it will be discovered automatically at startup.
The hash identifies exact bytes; it does not prove that code is safe. A local
wheel remains visibly unverified after consent. It can access APIs exposed by
Pyodide and the browser origin. Disable or remove it from the manager to unload
its contributions and managed import modules; removing it also deletes its
OPFS artifact and revokes its stored trust decision.
A compatible wheel exposes at least one entry point in the dedicated Web
group. A distribution shared with Desktop may publish both groups; the browser
imports only datalab.web_plugins and never imports the Qt target.
[project.entry-points."datalab.web_plugins"]
my-plugin = "my_package.adapters.web:MyWebPlugin"
[project.entry-points."datalab.plugins"]
my-plugin = "my_package.adapters.desktop:MyDesktopPlugin"The Web target must be a subclass of the portable PluginBase and provide a
stable namespaced PluginInfo.id. Application plugins include
PluginCapability.APPLICATION, declare RECIPES and optionally EXAMPLES,
and may implement materialize_example(). Recipe slots carry signal/image type, ONE/MANY cardinality, required state, a title and description, a minimum count, and metadata requirements; recipes may add suggest_bindings and check_inputs hooks. An example lists the recipes it is designed for in recipe_ids, and generated examples key their parameter values by recipe ID. Parameters are guidata DataSet classes. Outputs are returned as a
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.plugins.recipe_binding module as DataLab Desktop, copied into the host shim, so both hosts accept the same selections.
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 withedit_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 adatalab.plugins.instruments.PluginInstrument, which holds its settings in a guidataDataSetand implementspreview()(anInstrumentFrame: signals drawn together, or one image, with a summary and an optional fixed range) andacquire()(anInstrumentAcquisition: 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 everylive_interval_ms, a New group option and an Acquire button. Each acquisition goes to a new group or, when the user turns New group off, to the current group of the panel (a new group is still created when the panel has no current group). Thenew_groupclass attribute of the instrument sets the default state of the option (Trueby default); the choice of the user is remembered for each instrument and prevails. 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.plugins.tools, datalab.plugins.instruments and datalab.plugins.resources are copied from DataLab Desktop into the host shim, like datalab.plugins.recipe_binding.
The wheel rules below come from datalab.plugins.wheels, also copied verbatim from DataLab Desktop and used by the plugin catalog, so a wheel is judged the same way everywhere; dlw_wheels.py only applies them to the datalab.web_plugins group. A Python test compares the copy with a sibling DataLab checkout when one is available.
Version 1 of the installer deliberately accepts only local *-none-any
pure-Python wheels compatible with Pyodide's Python version. Native payloads,
archive traversal, reserved host namespaces, and distribution/package/plugin
collisions are rejected. Every Requires-Dist dependency must already be
provided by the host at a compatible version. There is no package-index lookup,
dependency resolution, network download, or implicit installation of a second
wheel.
Single-file built-ins are not sufficient for a plugin that has a reusable
core -> workflow -> adapters package. DataLab-Web therefore ships qualified
multi-module plugins as explicit wheel assets under
src/runtime/builtin_wheels/. Each artifact has a checked version, size, and
SHA-256 entry in bundledPlugins.ts. The runtime fetches only the Vite-bundled
URL and passes the wheel through the same inspector and managed registry as a
local wheel before importing its Web entry point.
The Camera and Pulse characterization wheels are the first such bundles. They
are deliberately not installed from a public package index and their Desktop
entry points are not used in the browser. DataLab-Web supplies the portable
datalab.* host shim. Camera reuses the existing HDF5 byte loader for its
packaged quickstart, while Pulse creates its documented deterministic campaign
through the shared headless simulator. See
src/runtime/builtin_wheels/README.md for the update procedure.
Bundling proves distribution, not compatibility. A bundled artifact receives
the registry trust state verified only after its visible outputs and Pyodide
resource budget have passed the project-specific qualification gate.
The Camera gate in tests/e2e/camera_bundle.spec.ts runs the packaged campaign
through the real worker-hosted Pyodide runtime. It requires a visible Plotly
response trace, decoded non-blank PRNU-map pixels, and the anchored metrics
table in the Results panel. It also limits incremental WASM-heap growth to
64 MiB and retained output arrays to three times the input arrays. The status
qualifies DataLab-Web 0.8.0, Pyodide 0.26.4, Camera 0.2.0, and relative-DN
recipe 1.1.0.
The Pulse gate in tests/e2e/pulse_bundle.spec.ts executes the deterministic
500-shot campaign through Pyodide and transactionally commits three signal
outputs plus the anchored 500-row metrics table. It requires visible Plotly
traces for amplitude, raw mean, and aligned mean; all six quality statuses;
489 valid/aligned shots; no more than 64 MiB incremental WASM heap; and exactly
24,032 bytes of retained output arrays. This qualifies DataLab-Web 0.8.0,
Pyodide 0.26.4, Pulse 0.2.0, and pulse campaign recipe 1.1.0.
Since version 0.2.0, Camera also offers photon transfer and dark-current recipes, and Pulse offers shot-to-shot stability, step-response, two-channel delay and pulse-height spectrum recipes, each with a generated example. tests/e2e/application_methods.spec.ts opens every new example through its deep link, checks that its method is ready, runs it from the Applications dialog and checks the created outputs in the visible object tree; it also runs the relative-DN method through Try with this example on the photon transfer ladder. tests/python/test_camera_application.py and tests/python/test_pulse_application.py run the same pairs, plus the photon transfer → relative-DN and laser warm-up → campaign pairs, through the generic host and check that every example binds its recipe slots without ambiguity.
Plugins → Reload all plugins re-imports every loaded plugin module from disk. The plugin registry strips all features and menu entries contributed by previous versions before re-applying them, so live edits are safe.