Summary
Introduce a small registry that lets applications plug an alternate UI backend in front of DataSet.edit, DataSet.view and DataSetGroup.edit, without changing the default Qt-based behaviour. This unlocks running guidata-based applications in environments where Qt is unavailable (typically a browser/React UI driven by Pyodide), while keeping every existing Qt application strictly unchanged.
Motivation
guidata.dataset.DataSet is the canonical way to declare parameter sets across the PlotPyStack and downstream projects (DataLab, Sigima, PlotPy, …). Today, DataSet.edit(), DataSet.view() and DataSetGroup.edit() are hard-wired to the Qt widgets shipped in guidata.dataset.qtwidgets. As soon as a downstream project wants to reuse the same parameter declarations from a non-Qt UI — for example DataLab-Web, which renders the UI with React inside Pyodide — there is no extension point: the only options are to monkey-patch the methods or to fork DataSet.
A first-class extension point keeps a single source of truth for parameter declarations across native (Qt) and web (React) frontends, and avoids importing Qt at all on platforms where it is not available.
Proposed change
Add a tiny handler registry under guidata.dataset.backends and consult it from the three dialog entry points before falling back to the existing Qt implementation. When no handler is registered, behaviour is byte-for-byte identical to today.
Recognised slot names:
edit_dataset — synchronous edition of a DataSet. Signature: handler(instance, *, parent, apply, wordwrap, size, object_name) -> int.
view_dataset — read-only display of a DataSet. Signature: handler(instance, *, parent, wordwrap, size) -> int.
edit_dataset_group — synchronous edition of a DataSetGroup. Signature: handler(instance, *, parent, apply, wordwrap, size, mode) -> int.
edit_dataset_async — asynchronous edition of a DataSet, returning an awaitable. Used by the new DataSet.edit_async() coroutine, which falls back to the synchronous handler (or to Qt) when no async handler is registered.
Handlers are looked up at call time, so they may be installed after DataSet instances have been created. Return values follow the existing Qt convention (1 = accepted, 0 = rejected) so callers do not have to special-case the backend in use.
The registry exposes a minimal API: set_handler, get_handler, has_handler, clear_handler, clear_all_handlers.
Why an async variant?
Browser event loops cannot block the calling thread to wait for a modal dialog: DataSet.edit() would deadlock the page. Adding DataSet.edit_async() lets non-Qt backends expose a coroutine that resolves once the user closes the React/HTML dialog, while Qt applications are free to keep using the synchronous edit() exactly as before. When no async handler is registered, edit_async() simply delegates to edit() so the new method is always safe to call.
Backwards compatibility
- No public signature is changed.
DataSet.edit, DataSet.view and DataSetGroup.edit keep their current parameters and defaults.
- The Qt code paths are only entered when no handler is registered, which is the default. Existing Qt applications observe no behavioural difference.
- Qt remains an optional dependency: importing
guidata.dataset.backends does not import Qt.
Test plan
Tests added in guidata/tests/dataset/test_backends.py cover:
- Registry semantics (
set_handler / get_handler / has_handler / clear_handler).
DataSet.edit delegating to a registered handler and forwarding the documented keyword arguments.
DataSet.view delegating to a registered handler.
DataSet.edit_async falling back to the synchronous handler when no async handler is registered.
DataSet.edit_async preferring a dedicated async handler when one is registered.
DataSetGroup.edit delegating to a registered handler.
A fixture clears the registry around each test to guarantee isolation.
Files changed
guidata/dataset/backends.py (new) — handler registry and module documentation.
guidata/dataset/datatypes.py — consult the registry from DataSet.edit, DataSet.view, DataSetGroup.edit; add DataSet.edit_async.
guidata/tests/dataset/test_backends.py (new) — unit tests for the registry and the delegation logic.
Out of scope
- Shipping a concrete non-Qt backend in guidata itself. The DataLab-Web React backend will live in its own repository and only depend on this extension point.
- Async variants of
view() and DataSetGroup.edit(). They can be added later following the same pattern if a concrete need arises.
- Reworking the Qt dialogs.
Summary
Introduce a small registry that lets applications plug an alternate UI backend in front of
DataSet.edit,DataSet.viewandDataSetGroup.edit, without changing the default Qt-based behaviour. This unlocks running guidata-based applications in environments where Qt is unavailable (typically a browser/React UI driven by Pyodide), while keeping every existing Qt application strictly unchanged.Motivation
guidata.dataset.DataSetis the canonical way to declare parameter sets across the PlotPyStack and downstream projects (DataLab, Sigima, PlotPy, …). Today,DataSet.edit(),DataSet.view()andDataSetGroup.edit()are hard-wired to the Qt widgets shipped inguidata.dataset.qtwidgets. As soon as a downstream project wants to reuse the same parameter declarations from a non-Qt UI — for example DataLab-Web, which renders the UI with React inside Pyodide — there is no extension point: the only options are to monkey-patch the methods or to forkDataSet.A first-class extension point keeps a single source of truth for parameter declarations across native (Qt) and web (React) frontends, and avoids importing Qt at all on platforms where it is not available.
Proposed change
Add a tiny handler registry under
guidata.dataset.backendsand consult it from the three dialog entry points before falling back to the existing Qt implementation. When no handler is registered, behaviour is byte-for-byte identical to today.Recognised slot names:
edit_dataset— synchronous edition of aDataSet. Signature:handler(instance, *, parent, apply, wordwrap, size, object_name) -> int.view_dataset— read-only display of aDataSet. Signature:handler(instance, *, parent, wordwrap, size) -> int.edit_dataset_group— synchronous edition of aDataSetGroup. Signature:handler(instance, *, parent, apply, wordwrap, size, mode) -> int.edit_dataset_async— asynchronous edition of aDataSet, returning an awaitable. Used by the newDataSet.edit_async()coroutine, which falls back to the synchronous handler (or to Qt) when no async handler is registered.Handlers are looked up at call time, so they may be installed after
DataSetinstances have been created. Return values follow the existing Qt convention (1= accepted,0= rejected) so callers do not have to special-case the backend in use.The registry exposes a minimal API:
set_handler,get_handler,has_handler,clear_handler,clear_all_handlers.Why an async variant?
Browser event loops cannot block the calling thread to wait for a modal dialog:
DataSet.edit()would deadlock the page. AddingDataSet.edit_async()lets non-Qt backends expose a coroutine that resolves once the user closes the React/HTML dialog, while Qt applications are free to keep using the synchronousedit()exactly as before. When no async handler is registered,edit_async()simply delegates toedit()so the new method is always safe to call.Backwards compatibility
DataSet.edit,DataSet.viewandDataSetGroup.editkeep their current parameters and defaults.guidata.dataset.backendsdoes not import Qt.Test plan
Tests added in
guidata/tests/dataset/test_backends.pycover:set_handler/get_handler/has_handler/clear_handler).DataSet.editdelegating to a registered handler and forwarding the documented keyword arguments.DataSet.viewdelegating to a registered handler.DataSet.edit_asyncfalling back to the synchronous handler when no async handler is registered.DataSet.edit_asyncpreferring a dedicated async handler when one is registered.DataSetGroup.editdelegating to a registered handler.A fixture clears the registry around each test to guarantee isolation.
Files changed
guidata/dataset/backends.py(new) — handler registry and module documentation.guidata/dataset/datatypes.py— consult the registry fromDataSet.edit,DataSet.view,DataSetGroup.edit; addDataSet.edit_async.guidata/tests/dataset/test_backends.py(new) — unit tests for the registry and the delegation logic.Out of scope
view()andDataSetGroup.edit(). They can be added later following the same pattern if a concrete need arises.