Data set examples

Basic example

Source code :

# -*- coding: utf-8 -*-

import guidata
import guidata.dataset as gds

# Note: the following line is not required if a QApplication has already been created
_app = guidata.qapplication()


class Processing(
    gds.DataSet,
    title="Processing Parameters",
    comment="This comment concerns this class of parameters, and may be overriden "
    "when calling the constructor by passing a comment argument. That is the same for "
    "the title.",
):
    """Example"""

    a = gds.FloatItem("Parameter #1", default=2.3)
    b = gds.IntItem("Parameter #2", min=0, max=10, default=5)
    type = gds.ChoiceItem("Processing algorithm", ("type 1", "type 2", "type 3"))


param = Processing()  # Default title and comment are used if not provided here
param.edit()
print(param)  # Showing param contents
param.b = 4  # Modifying item value
param.view()

# Alternative way for creating a DataSet instance:
param = Processing.create(a=7.323, b=4)
print(param)
_images/basic_example.png

Other examples

A lot of examples are available in the guidata test module

from guidata import tests
tests.run()

The two lines above execute the guidata test launcher :

_images/__init__.png

All guidata items demo


import atexit
import datetime
import shutil
import tempfile
from enum import Enum

import numpy as np

import guidata.dataset as gds
from guidata.config import _
from guidata.env import execenv
from guidata.qthelpers import qt_app_context

# Creating temporary files and registering cleanup functions
TEMPDIR = tempfile.mkdtemp(prefix="test_")
atexit.register(shutil.rmtree, TEMPDIR)
FILE_ETA = tempfile.NamedTemporaryFile(suffix=".eta", dir=TEMPDIR)
atexit.register(FILE_ETA.close)
FILE_CSV = tempfile.NamedTemporaryFile(suffix=".csv", dir=TEMPDIR)
atexit.register(FILE_CSV.close)


class ImageTypes(Enum):
    """Image types."""

    #: Image filled with zeros
    ZEROS = _("Zeros")
    #: Empty image (filled with data from memory state)
    EMPTY = _("Empty")
    #: Image filled with random data (uniform law)
    UNIFORMRANDOM = _("Random (uniform law)")
    #: Image filled with random data (normal law)
    NORMALRANDOM = _("Random (normal law)")
    #: 2D Gaussian image
    GAUSS = _("Gaussian")
    #: Bilinear form image
    RAMP = _("2D ramp")


class FilteringMethod(gds.LabeledEnum):
    """LabeledEnum example with invariant keys and UI labels."""

    GAUSSIAN = "gaussian", _("Gaussian filter")
    MEDIAN = "median", _("Median filter")
    SOBEL = "sobel", _("Sobel edge detection")
    LAPLACIAN = "laplacian", _("Laplacian filter")
    BILATERAL = "bilateral", _("Bilateral filter")
    MORPHOLOGICAL = "morphological"  # Uses key as label


class ThresholdMode(gds.LabeledEnum):
    """Another LabeledEnum example."""

    OTSU = "otsu_method", _("Otsu's method")
    ADAPTIVE = "adaptive_mean", _("Adaptive mean")
    GLOBAL = "global_threshold", _("Global threshold")
    TRIANGLE = "triangle_algorithm", _("Triangle algorithm")


class Parameters(gds.DataSet):
    """
    DataSet test
    The following text is the DataSet 'comment': <br>Plain text or
    <b>rich text<sup>2</sup></b> are both supported,
    as well as special characters (α, β, γ, δ, ...)
    """

    dir = gds.DirectoryItem("Directory", default=TEMPDIR)
    preview = gds.TextItem("File names preview", default="-")
    option = gds.ChoiceItem("Option", (("1", "first choice"), ("2", "second choice")))

    fname = gds.FileOpenItem("Open file", ("csv", "eta"), FILE_CSV.name)
    fnames = gds.FilesOpenItem("Open files", "csv", [FILE_CSV.name])
    fname_s = gds.FileSaveItem("Save file", "eta", FILE_ETA.name)
    string = gds.StringItem("String", default="default string !?")
    text = gds.TextItem("Text", default="default\nmultiline\ntext")
    float_slider = gds.FloatItem(
        "Float (with slider)", default=0.5, min=0, max=1, step=0.01, slider=True
    )
    integer = gds.IntItem("Integer", default=5, min=3, max=16, slider=True).set_pos(
        col=1
    )
    dtime = gds.DateTimeItem("Date/time", default=datetime.datetime(2010, 10, 10))
    date = gds.DateItem("Date", default=datetime.date(2010, 10, 10)).set_pos(col=1)
    bool1 = gds.BoolItem("Boolean option without label")
    bool2 = gds.BoolItem("Boolean option with label", "Label")
    _bg = gds.BeginGroup("A sub group")
    color = gds.ColorItem("Color", default="red")
    choice1 = gds.ChoiceItem(
        "Single choice 1",
        [("16", "first choice"), ("32", "second choice"), ("64", "third choice")],
    )
    choice2 = gds.ChoiceItem("Image type", ImageTypes, default=ImageTypes.GAUSS)

    # LabeledEnum examples - Pattern 1 with invariant keys and UI labels
    filter_method = gds.ChoiceItem(
        "Filtering method", FilteringMethod, default=FilteringMethod.GAUSSIAN
    )
    threshold_mode = gds.ChoiceItem(
        "Threshold mode", ThresholdMode, default=ThresholdMode.OTSU
    )

    mchoice2 = gds.ImageChoiceItem(
        "Single choice 2",
        [
            ("rect", "first choice", "gif.png"),
            ("ell", "second choice", "txt.png"),
            ("qcq", "third choice", "file.png"),
        ],
    )
    _eg = gds.EndGroup("A sub group")
    floatarray = gds.FloatArrayItem(
        "Float array", default=np.ones((50, 5), float), format=" %.2e "
    ).set_pos(col=1)
    mchoice3 = gds.MultipleChoiceItem(
        "MC type 1", [str(i) for i in range(12)]
    ).horizontal(4)
    mchoice1 = (
        gds.MultipleChoiceItem(
            "MC type 2", ["first choice", "second choice", "third choice"]
        )
        .vertical(1)
        .set_pos(col=1)
    )
    dictionary = gds.DictItem(
        "Dictionary",
        default={
            "lkl": 2,
            "tototo": 3,
            "zzzz": "lklk",
            "bool": True,
            "float": 1.234,
            "list": [1, 2.5, 3, "str", False, 5, {"lkl": 2, "l": [1, 2, 3]}],
        },
        help="This is a dictionary",
    )


def test_all_items():
    """Test all DataItem objects"""
    with qt_app_context():
        prm = Parameters()

        prm.floatarray[:, 0] = np.linspace(-5, 5, 50)

        # Demonstrate LabeledEnum functionality
        execenv.print("=== LabeledEnum Pattern 1 Demo ===")
        execenv.print(f"Filter method (enum member): {prm.filter_method!r}")
        execenv.print(f"Filter value (invariant key): {prm.filter_method.value!r}")
        execenv.print(f"Filter label (UI display): {prm.filter_method.label!r}")
        execenv.print(f"Filter str(): {str(prm.filter_method)!r}")

        # Show different ways to set LabeledEnum values
        prm.filter_method = "sobel"  # Set by invariant key
        execenv.print(f"Set by key 'sobel': {prm.filter_method!r}")

        prm.filter_method = _("Median filter")  # Set by UI label
        execenv.print(f"Set by label 'Median filter': {prm.filter_method!r}")

        prm.filter_method = 0  # Set by index
        execenv.print(f"Set by index 0: {prm.filter_method!r}")

        execenv.print("=== End LabeledEnum Demo ===")

        execenv.print(prm)
        if prm.edit():
            execenv.print(prm)
        prm.view()
        execenv.print("OK")


if __name__ == "__main__":
    test_all_items()
_images/all_items.png

Embedding guidata objects in GUI layouts


import numpy as np
from qtpy.QtWidgets import QMainWindow, QSplitter

import guidata.dataset as gds
from guidata.configtools import get_icon
from guidata.dataset.qtwidgets import DataSetEditGroupBox, DataSetShowGroupBox
from guidata.env import execenv
from guidata.qthelpers import (
    add_actions,
    create_action,
    get_std_icon,
    qt_app_context,
    win32_fix_title_bar_background,
)
from guidata.tests.dataset.test_activable_dataset import Parameters
from guidata.widgets import about


class AnotherDataSet(gds.DataSet):
    """
    Example 2
    <b>Simple dataset example</b>
    """

    param0 = gds.ChoiceItem("Choice", ["deazdazk", "aeazee", "87575757"])
    param1 = gds.FloatItem("Foobar 1", default=0, min=0)
    a_group = gds.BeginGroup("A group")
    param2 = gds.FloatItem("Foobar 2", default=0.93)
    param3 = gds.FloatItem("Foobar 3", default=123)
    _a_group = gds.EndGroup("A group")


class ExampleMultiGroupDataSet(gds.DataSet):
    """Example DS with multiple groups"""

    choices = gds.MultipleChoiceItem("Choices", ["a", "b", "c", "d", "e"])
    dictionary = gds.DictItem("Dictionary", {"a": 1, "b": 2, "c": 3})
    param0 = gds.ChoiceItem("Choice", ["deazdazk", "aeazee", "87575757"])
    param1 = gds.FloatItem("Foobar 1", default=0, min=0)
    t_group = gds.BeginTabGroup("T group")
    a_group = gds.BeginGroup("A group")
    param2 = gds.FloatItem("Foobar 2", default=0.93)
    dir1 = gds.DirectoryItem("Directory 1")
    file1 = gds.FileOpenItem("File 1")
    _a_group = gds.EndGroup("A group")
    b_group = gds.BeginGroup("B group")
    param3 = gds.FloatItem("Foobar 3", default=123)
    param4 = gds.BoolItem("Boolean")
    _b_group = gds.EndGroup("B group")
    c_group = gds.BeginGroup("C group")
    param5 = gds.FloatItem("Foobar 4", default=250)
    param6 = gds.DateItem("Date").set_prop("display", format="dd.MM.yyyy")
    param7 = gds.ColorItem("Color")
    _c_group = gds.EndGroup("C group")
    _t_group = gds.EndTabGroup("T group")


class OtherDataSet(gds.DataSet):
    """Another example dataset"""

    group1 = gds.BeginGroup("Group 1")
    title = gds.StringItem("Title", default="Title")
    icon = gds.ChoiceItem(
        "Icon",
        (
            ("python.png", "Python"),
            ("guidata.svg", "guidata"),
            ("settings.png", "Settings"),
        ),
    )
    opacity = gds.FloatItem("Opacity", default=1.0, min=0.1, max=1)
    transform = gds.FloatArrayItem("Transform", default=np.array([1, 2, 3, 4, 5, 6]))
    _group1 = gds.EndGroup("Group 1")
    group2 = gds.BeginGroup("Group 2")
    computed = gds.StringItem("Computed").set_computed(
        lambda ds: f"T: {ds.title}, I: {ds.icon}, O: {ds.opacity}"
    )
    _group2 = gds.EndGroup("Group 2")


class MainWindow(QMainWindow):
    """Main window"""

    def __init__(self):
        QMainWindow.__init__(self)
        win32_fix_title_bar_background(self)
        self.setWindowIcon(get_icon("python.png"))
        self.setWindowTitle("Application example")

        # Instantiate dataset-related widgets:
        self.groupbox1 = DataSetShowGroupBox(
            "Activable dataset", Parameters, comment=""
        )
        self.groupbox2 = DataSetShowGroupBox(
            "Standard dataset", AnotherDataSet, comment=""
        )
        self.groupbox3 = DataSetEditGroupBox(
            "Standard dataset", OtherDataSet, comment=""
        )
        self.groupbox4 = DataSetEditGroupBox(
            "Standard dataset", ExampleMultiGroupDataSet, comment=""
        )
        self.groupbox3.SIG_APPLY_BUTTON_CLICKED.connect(self.update_window)
        self.update_groupboxes()

        splitter = QSplitter(self)
        splitter.addWidget(self.groupbox1)
        splitter.addWidget(self.groupbox2)
        splitter.addWidget(self.groupbox3)
        splitter.addWidget(self.groupbox4)
        self.setCentralWidget(splitter)
        self.setContentsMargins(10, 5, 10, 5)

        # File menu
        file_menu = self.menuBar().addMenu("File")
        quit_action = create_action(
            self,
            "Quit",
            shortcut="Ctrl+Q",
            icon=get_std_icon("DialogCloseButton"),
            tip="Quit application",
            triggered=self.close,
        )
        add_actions(file_menu, (quit_action,))

        # Edit menu
        edit_menu = self.menuBar().addMenu("Edit")
        editparam1_action = create_action(
            self, "Edit dataset 1", triggered=self.edit_dataset1
        )
        editparam2_action = create_action(
            self, "Edit dataset 2", triggered=self.edit_dataset2
        )
        editparam4_action = create_action(
            self, "Edit dataset 4", triggered=self.edit_dataset4
        )
        ro_param4_action = create_action(
            self, "Dataset 4: read-only", toggled=self.ro_dataset4
        )
        add_actions(
            edit_menu,
            (editparam1_action, editparam2_action, editparam4_action, ro_param4_action),
        )

        # ? menu
        help_menu = self.menuBar().addMenu("?")
        about_action = create_action(
            self,
            "About guidata",
            icon=get_std_icon("MessageBoxInformation"),
            triggered=about.show_about_dialog,
        )
        add_actions(help_menu, (about_action,))

    def update_window(self):
        """Update window"""
        dataset = self.groupbox3.dataset
        self.setWindowTitle(dataset.title)
        self.setWindowIcon(get_icon(dataset.icon))
        self.setWindowOpacity(dataset.opacity)

    def update_groupboxes(self):
        """Update groupboxes"""
        self.groupbox1.dataset.set_activable(False)  # This is an activable dataset
        self.groupbox1.get()
        self.groupbox2.get()
        self.groupbox4.get()

    def ro_dataset4(self, readonly: bool):
        self.groupbox4.dataset.set_readonly(readonly)
        self.groupbox4.get()

    def edit_dataset1(self):
        """Edit dataset 1"""
        self.groupbox1.dataset.set_activable(True)  # This is an activable dataset
        if self.groupbox1.dataset.edit(self):
            self.update_groupboxes()

    def edit_dataset2(self):
        """Edit dataset 2"""
        if self.groupbox2.dataset.edit(self):
            self.update_groupboxes()

    def edit_dataset4(self):
        """Edit dataset 4"""
        if self.groupbox4.dataset.edit(self):
            self.update_groupboxes()


def test_editgroupbox():
    """Test editgroupbox"""
    with qt_app_context(exec_loop=True):
        window = MainWindow()
        window.show()
        execenv.print("OK")


if __name__ == "__main__":
    test_editgroupbox()
_images/editgroupbox.png

Local automatic sliders

An embedded DataSetEditLayout can opt into sliders without changing shared DataItem declarations or other forms:

editor = DataSetEditLayout(
   parent, parameters, grid, change_callback=parameters_changed,
   auto_sliders=True, slider_steps=1000,
)

The policy is inherited by nested groups and tabs. Editable numeric items need finite, ordered, representable bounds; otherwise they remain text-only. Integer parity is respected, and ranges crossing zero for nonzero items are not automatically given sliders. For floats, a usable positive step is used when practical, otherwise slider_steps specifies the normalized resolution. The text field retains its exact value independently of the slider thumb.

slider=True retains its existing behavior. Set item.set_prop("display", auto_slider=False) to exclude an item from the local automatic policy. No bounds are inferred. The optional layout callback slider_callback(pressed) receives True at drag start and False at release; value changes still use the existing change_callback. Neither callback automatically validates, accepts or applies the form. Use check_all_values() before accept_changes() when collecting a draft.

Histogram-backed interval selection

HistogramRangeItem edits two distinct FloatItem fields in the same dataset. Its default presentation is a generic interval, suitable for measurements such as durations. The caller supplies histogram counts and range proposals; guidata does not inspect source arrays or choose an automatic range.

The following tested dataset has bounded durations in seconds and a live dependent field. Import guidata.dataset as gds before using it:

class DurationRange(gds.DataSet):
    """Select a duration interval from a caller-supplied histogram."""

    minimum = gds.FloatItem(
        "Minimum", default=0.0, min=0.0, max=10.0, unit="s"
    ).set_prop("display", hide=True)
    maximum = gds.FloatItem(
        "Maximum", default=10.0, min=0.0, max=10.0, unit="s"
    ).set_prop("display", hide=True)
    width = gds.FloatItem("Selected duration", default=10.0, unit="s").set_prop(
        "display", readonly=True
    )

    def update_width(self, item, value):
        """Refresh the dependent field from the complete edited pair."""
        self.width = self.maximum - self.minimum

    histogram = gds.HistogramRangeItem(
        "Durations",
        "minimum",
        "maximum",
        default={
            "counts": [1, 4, 3, 2],
            "domain": [0.0, 10.0],
            "minimum_width": 0.01,
            "active": True,
            "auto_range": [2.0, 8.0],
            "reset_range": [0.0, 10.0],
        },
    ).set_prop("display", callback=update_width)

Call DurationRange().edit() to display it. The hidden numeric fields retain their constraints, including nonzero and the check=False opt-out. A window outside those constraints stays visible but cannot be accepted. The histogram’s domain is only a drawing/slider domain, not an additional numeric constraint. If either linked field is read-only or inactive, the entire composite is disabled.

The histogram payload is transient: only the linked numeric values are serialized. After loading a dataset, restore its histogram context before enabling editing. An empty context leaves the saved bounds visible and the controls disabled. Auto and Reset appear only when the caller supplies finite ordered proposals.

The callback receives (instance, item, payload) once per valid changed pair. Both bounds are already on the working dataset, and dependent fields are refreshed. Without callbacks or computed fields, edits remain local until acceptance. With live dependencies, guidata updates the working dataset before acceptance, just as for ordinary items. Applications requiring transactional Cancel must edit a copy; the dialog cannot undo arbitrary callback side effects.

For image applications, explicitly select the brightness/contrast presentation:

DurationRange.histogram.set_prop("display", presentation="brightness_contrast")

This adds brightness/contrast controls and a linear transfer overlay. It changes only rendering metadata, never the stored parameters. The JSON Schema exporter emits x-guidata-histogram-presentation so portable renderers use the same mode.

Data item groups and group selection


import guidata.dataset as gds
from guidata.env import execenv
from guidata.qthelpers import qt_app_context

prop1 = gds.ValueProp(False)
prop2 = gds.ValueProp(False)


class Parameters(gds.DataSet):
    """
    Group selection test
    <b>Group selection example:</b>
    """

    g1 = gds.BeginGroup("group 1")
    enable1 = gds.BoolItem(
        "Enable parameter set #1",
        help="If disabled, the following parameters will be ignored",
        default=False,
    ).set_prop("display", store=prop1)
    prm11 = gds.FloatItem("Prm 1.1", default=0, min=0).set_prop("display", active=prop1)
    prm12 = gds.FloatItem("Prm 1.2", default=0.93).set_prop("display", active=prop1)
    _g1 = gds.EndGroup("group 1")
    g2 = gds.BeginGroup("group 2")
    enable2 = gds.BoolItem(
        "Enable parameter set #2",
        help="If disabled, the following parameters will be ignored",
        default=True,
    ).set_prop("display", store=prop2)
    prm21 = gds.FloatItem("Prm 2.1", default=0, min=0).set_prop("display", active=prop2)
    prm22 = gds.FloatItem("Prm 2.2", default=0.93).set_prop("display", active=prop2)
    _g2 = gds.EndGroup("group 2")


def test_bool_selector():
    """Test bool selector"""
    with qt_app_context():
        prm = Parameters()
        prm.edit()
        execenv.print(prm)
        execenv.print("OK")


if __name__ == "__main__":
    test_bool_selector()
_images/bool_selector.png

Activable data sets



class Parameters(gds.ActivableDataSet):
    """
    Example
    <b>Activable dataset example</b>
    """

    def __init__(self, title=None, comment=None, icon=""):
        gds.ActivableDataSet.__init__(self, title, comment, icon)

    enable = gds.BoolItem(
        "Enable parameter set",
        help="If disabled, the following parameters will be ignored",
        default=False,
    )
    param0 = gds.ChoiceItem("Param 0", ["choice #1", "choice #2", "choice #3"])
    param1 = gds.FloatItem("Param 1", default=0, min=0)
    param2 = gds.FloatItem("Param 2", default=0.93)
    color = gds.ColorItem("Color", default="red")


Parameters.active_setup()


def test_activable_dataset():
    """Test activable dataset"""
    with qt_app_context():
        prm = Parameters()
        prm.set_activable(True)
        prm.edit()
        prm.set_activable(False)
        prm.edit()
        prm.set_readonly()
        prm.edit()
        prm.set_readonly(False)
        prm.edit()
        execenv.print("OK")


if __name__ == "__main__":
    test_activable_dataset()
_images/activable_dataset.png

Data set groups


from guidata.dataset import DataSetGroup
from guidata.env import execenv
from guidata.qthelpers import qt_app_context
from guidata.tests.dataset.test_all_features import Parameters


def test_dataset_group():
    """Test DataSetGroup"""
    with qt_app_context():
        e1 = Parameters("DataSet #1")
        e2 = Parameters("DataSet #2")

        g = DataSetGroup([e1, e2], title="Parameters group")
        g.edit()
        execenv.print(e1)
        g.edit()
        execenv.print("OK")

        g = DataSetGroup([e1, e2], title="Parameters group in table mode")
        g.edit(mode="table")
        execenv.print(e1)
        g.edit()
        execenv.print("OK")

        g.edit()
        execenv.print(e1)
        g.edit()
        execenv.print("OK")


if __name__ == "__main__":
    test_dataset_group()
_images/datasetgroup.png

Utilities

Update/restore a dataset from/to a dictionary


import numpy as np

from guidata.dataset.conv import restore_dataset, update_dataset
from guidata.tests.dataset.test_all_items import Parameters


def test_update_restore_dataset():
    """Test update/restore dataset from/to another dataset or dictionary"""
    dataset = Parameters()
    dsdict = {
        "integer": 1,
        "float_slider": 1.0,
        "bool1": True,
        "string": "test",
        "floatarray": np.array([1, 2, 3]),
        "dictionary": {"a": 1, "b": 2},
    }
    # Update dataset from dictionary
    update_dataset(dataset, dsdict)
    # Check dataset values
    assert dataset.integer == dsdict["integer"]
    assert dataset.float_slider == dsdict["float_slider"]
    assert dataset.bool1 == dsdict["bool1"]
    assert dataset.string == dsdict["string"]
    assert np.all(dataset.floatarray == dsdict["floatarray"])
    assert dataset.dictionary == dsdict["dictionary"]
    # Update dataset from another dataset
    dataset2 = Parameters()
    update_dataset(dataset2, dataset)
    # Check dataset values
    assert dataset2.integer == dataset.integer
    assert dataset2.float_slider == dataset.float_slider
    assert dataset2.bool1 == dataset.bool1
    assert dataset2.string == dataset.string
    assert np.all(dataset2.floatarray == dataset.floatarray)
    assert dataset2.dictionary == dataset.dictionary
    # Restore dataset from dictionary
    restore_dataset(dataset, dsdict)
    # Check dataset values
    assert dataset.integer == dsdict["integer"]
    assert dataset.float_slider == dsdict["float_slider"]
    assert dataset.bool1 == dsdict["bool1"]
    assert dataset.string == dsdict["string"]
    assert np.all(dataset.floatarray == dsdict["floatarray"])
    assert dataset.dictionary == dsdict["dictionary"]


if __name__ == "__main__":
    test_update_restore_dataset()

Create a dataset class from a function signature


from __future__ import annotations

import numpy as np

from guidata.dataset import create_dataset_from_func
from guidata.env import execenv


def func_ok(
    a: int, b: float, c: str = "test", d: dict[str, int] = {"x": 1, "y": 3}
) -> None:
    """A function with type annotations"""
    pass


def func_no_type(a, b, c="test"):
    """A function without type annotations"""
    pass


def func_no_default(a: int, b: float, c: str, data: np.ndarray) -> None:
    """A function without default values"""
    pass


def test_dataset_from_func():
    """Test generate dataset class from function"""
    for func in (func_ok, func_no_default):
        execenv.print(func.__name__)
        dataset = create_dataset_from_func(func)
        execenv.print(dataset)
        execenv.print(dataset.create(a=1, b=2.0))
        execenv.print("")
    func = func_no_type
    try:
        create_dataset_from_func(func)
        assert False, "Should have raised a ValueError"
    except ValueError:
        # This is expected
        pass


if __name__ == "__main__":
    test_dataset_from_func()

Create a dataset class from a function dictionary


import numpy as np

from guidata.dataset import create_dataset_from_dict
from guidata.env import execenv

TEST_DICT1 = {
    "a": 1,
    "b": 2.0,
    "c": "test",
    "d": {"x": 1, "y": 3},
    "data": np.array([1, 2, 3]),
}
TEST_DICT2 = {
    "a": 1,
    "unsupported": [2.0, 3.0],
}


def test_dataset_from_dict():
    """Test generate dataset class from a dictionary"""
    for dictionary in (TEST_DICT1,):
        execenv.print(dictionary)
        dataset = create_dataset_from_dict(dictionary)
        execenv.print(dataset)
        execenv.print(dataset.create())
        execenv.print("")
    try:
        create_dataset_from_dict(TEST_DICT2)
        assert False, "Should have raised a ValueError"
    except ValueError:
        # This is expected
        pass


if __name__ == "__main__":
    test_dataset_from_dict()

Data set HDF5 serialization/deserialization


import os.path as osp
import tempfile

from guidata.dataset import assert_datasets_equal
from guidata.env import execenv
from guidata.io import HDF5Reader, HDF5Writer
from guidata.qthelpers import qt_app_context
from guidata.tests.dataset.test_all_items import Parameters


def test_loadsave_hdf5():
    """Test HDF5 I/O"""
    with tempfile.TemporaryDirectory() as temp_dir:
        fname = osp.join(temp_dir, "test.h5")
        with qt_app_context():
            p1 = Parameters()
            # p1.edit()

            # Save to HDF5 file
            writer = HDF5Writer(fname)
            p1.serialize(writer)
            writer.close()

            p2 = Parameters()
            # Set all items to None for testing purposes:
            for item in p2._items:
                item.__set__(p2, None)

            # Load from HDF5 file
            reader = HDF5Reader(fname)
            p2.deserialize(reader)
            reader.close()

            assert_datasets_equal(p1, p2, "Parameters do not match after HDF5 I/O")
            execenv.print("OK")


if __name__ == "__main__":
    test_loadsave_hdf5()

Data set JSON serialization/deserialization


import os

import guidata.dataset as gds
from guidata.env import execenv
from guidata.io import JSONReader, JSONWriter
from guidata.qthelpers import qt_app_context
from guidata.tests.dataset.test_all_items import Parameters


def test_loadsave_json():
    """Test JSON I/O"""
    fname = "test.json"
    with qt_app_context():
        if os.path.exists(fname):
            os.unlink(fname)

        p1 = Parameters()
        if execenv.unattended or p1.edit():
            writer = JSONWriter(fname)
            p1.serialize(writer)
            writer.save()

            p2 = Parameters()
            reader = JSONReader(fname)
            p2.deserialize(reader)
            reader.close()
            p2.edit()
            os.unlink(fname)

        # TODO: Uncomment this part of the test, and make it work!
        # if execenv.unattended:
        #     assert_datasets_equal(p1, p2, "Parameters do not match after HDF5 I/O")

        execenv.print("OK")


def test_transient_item_is_not_serialized():
    """Transient items are skipped by JSON serialization and deserialization."""

    class ParametersWithTransientItem(gds.DataSet):
        """Dataset holding a transient rendering payload beside persisted bounds."""

        minimum = gds.FloatItem("Minimum", default=0.0)
        maximum = gds.FloatItem("Maximum", default=1.0)
        histogram = gds.HistogramRangeItem(
            "Brightness and contrast", "minimum", "maximum"
        )

    source = ParametersWithTransientItem()
    source.minimum = 0.25
    source.maximum = 0.75
    source.histogram = {"counts": [1, 2, 3]}

    restored = gds.json_to_dataset(
        gds.dataset_to_json(source),
        strict=True,
        expected_class=ParametersWithTransientItem,
    )

    assert restored.minimum == 0.25
    assert restored.maximum == 0.75
    assert restored.histogram == {}


if __name__ == "__main__":
    test_loadsave_json()