Skip to content

magicclass.core

Parameters

Source code in magicclass\core.py
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
class Parameters:
    def __init__(self):
        self.__name__ = self.__class__.__name__
        self.__qualname__ = self.__class__.__qualname__

        sig = [
            inspect.Parameter(name="self", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD)
        ]
        for name, attr in inspect.getmembers(self):
            if name.startswith("__") or callable(attr):
                continue
            sig.append(
                inspect.Parameter(
                    name=name,
                    kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
                    default=attr,
                )
            )
        if hasattr(self.__class__, "__annotations__"):
            annot = self.__class__.__annotations__
            for name, t in annot.items():
                sig.append(
                    inspect.Parameter(
                        name=name,
                        kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
                        annotation=t,
                    )
                )

        self.__signature__ = inspect.Signature(sig)

    def __call__(self, *args) -> None:
        params = list(self.__signature__.parameters.keys())[1:]
        for a, param in zip(args, params):
            setattr(self, param, a)

    def as_dict(self) -> dict[str, Any]:
        """
        Convert parameter fields into a dictionary.

        >>> class params(Parameters):
        >>>     i = 1
        >>>     j = 2

        >>> p = params()
        >>> p.as_dict() # {"i": 1, "j": 2}
        """
        params = list(self.__signature__.parameters.keys())[1:]
        return {param: getattr(self, param) for param in params}
as_dict()

Convert parameter fields into a dictionary.

class params(Parameters): i = 1 j = 2

p = params() p.as_dict() # {"i": 1, "j": 2}

Source code in magicclass\core.py
715
716
717
718
719
720
721
722
723
724
725
726
727
def as_dict(self) -> dict[str, Any]:
    """
    Convert parameter fields into a dictionary.

    >>> class params(Parameters):
    >>>     i = 1
    >>>     j = 2

    >>> p = params()
    >>> p.as_dict() # {"i": 1, "j": 2}
    """
    params = list(self.__signature__.parameters.keys())[1:]
    return {param: getattr(self, param) for param in params}

build_help(ui, parent=None)

Build a widget for user guide. Once it is built, widget will be cached.

Parameters:

Name Type Description Default
ui MagicTemplate

Magic class UI object.

required

Returns:

Type Description
HelpWidget

Help of the input UI.

Source code in magicclass\core.py
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
def build_help(ui: MagicTemplate, parent=None) -> HelpWidget:
    """
    Build a widget for user guide. Once it is built, widget will be cached.

    Parameters
    ----------
    ui : MagicTemplate
        Magic class UI object.

    Returns
    -------
    HelpWidget
        Help of the input UI.
    """
    ui_id = id(ui)
    if ui_id in _HELPS.keys():
        help_widget = _HELPS[ui_id]
    else:
        from .help import HelpWidget

        if parent is None:
            parent = ui.native
        help_widget = HelpWidget(ui, parent=parent)
        _HELPS[ui_id] = help_widget
    return help_widget

get_button(ui, name=None, *, cache=False)

Get the button/action object for the given method.

This function is a helper function for magicclass. Using this method is always safer than directly accessing it by ui["method"]. Either of following expression is allowed.

get_button(ui, "method") get_button(ui.method)

Source code in magicclass\core.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
def get_button(
    ui: MagicTemplate | MethodType,
    name: str | None = None,
    *,
    cache: bool = False,
) -> Clickable:
    """
    Get the button/action object for the given method.

    This function is a helper function for magicclass. Using this method is
    always safer than directly accessing it by `ui["method"]`.
    Either of following expression is allowed.

    >>> get_button(ui, "method")
    >>> get_button(ui.method)

    """
    if name is None:
        if hasattr(ui, "__self__") and callable(ui):
            func = ui
            ui = func.__self__
            name = func.__name__
        else:
            raise TypeError(
                "The first argument of `get_button() must be a method if "
                "the method name is not given."
            )
    cache_key = (id(ui), name)
    if cache and (btn := _BUTTON_CACHE.get(cache_key, None) is not None):
        return btn

    widget: Clickable = ui[name]

    if not hasattr(widget, "mgui"):
        raise TypeError(f"Widget {widget} does not have FunctionGui inside it.")

    if widget._unwrapped:
        from magicclass.signature import get_additional_option

        opt = get_additional_option(getattr(ui, name), "into", None)
        if opt is not None:
            for _, child_instance in ui._iter_child_magicclasses():
                _name = child_instance.__class__.__name__
                if _name == opt:
                    widget = child_instance[name]
                    return widget
            raise ValueError(f"Could not find {opt} in {ui}.")
    if cache:
        _BUTTON_CACHE[cache_key] = widget
    return widget

get_function_gui(ui, name=None)

Get the FunctionGui object hidden beneath push button or menu action.

This function is a helper function for magicclass. Using this method is always safer than directly accessing it by ui["method"].mgui. Either of following expression is allowed.

get_function_gui(ui, "method") get_function_gui(ui.method)

Source code in magicclass\core.py
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
def get_function_gui(
    ui: MagicTemplate | MethodType,
    name: str | None = None,
) -> FunctionGuiPlus:
    """
    Get the FunctionGui object hidden beneath push button or menu action.

    This function is a helper function for magicclass. Using this method is
    always safer than directly accessing it by `ui["method"].mgui`.
    Either of following expression is allowed.

    >>> get_function_gui(ui, "method")
    >>> get_function_gui(ui.method)

    """
    if name is None:
        if hasattr(ui, "__self__") and callable(ui):
            func = ui
            ui = func.__self__
            name = func.__name__
        else:
            raise TypeError(
                "The first argument of `get_function_gui() must be a method if "
                "the method name is not given."
            )
    else:
        func = getattr(ui, name)
    widget: Clickable = ui[name]

    if not hasattr(widget, "mgui"):
        raise TypeError(f"Widget {widget} does not have FunctionGui inside it.")

    if widget.mgui is not None:
        return widget.mgui

    from ._gui._base import _build_mgui, _create_gui_method

    func = _create_gui_method(ui, func)
    mgui = _build_mgui(widget, func, ui)
    return mgui

magicclass(class_=None, *, layout='vertical', labels=True, name=None, visible=None, close_on_run=None, popup_mode=None, error_mode=None, widget_type=WidgetType.none, icon=None, stylesheet=None, properties=None, record=True, symbol='ui')

Decorator that can convert a Python class into a widget.

@magicclass class C: ... ui = C() ui.show() # open GUI

Parameters:

Name Type Description Default
class_ type

Class to be decorated.

None
layout (str, 'vertical' or 'horizontal')

Layout of the main widget.

"vertical"
labels bool

If true, magicgui labels are shown.

True
name str

Name of GUI.

None
visible bool

Initial visibility of GUI. Useful when magic class is nested.

None
close_on_run bool

If True, magicgui created by every method will be deleted after the method is completed without exceptions, i.e. magicgui is more like a dialog.

True
popup_mode str or PopUpMode

Option of how to popup FunctionGui widget when a button is clicked.

PopUpMode.popup
error_mode str or ErrorMode

Option of how to raise errors during function calls.

ErrorMode.msgbox
widget_type WidgetType or str

Widget type of container.

none
icon Any

Path to the icon image or any object that can be converted into an icon.

None
stylesheet str or StyleSheet object

Set stylesheet to the widget if given.

None
properties dict

Set properties to the widget if given. This argument is useful when you want to set width, height or margin without defining post_init.

None
record bool

If True, macro recording is enabled.

True
symbol str

The identifier used in macro to represent this widget.

"ui"

Returns:

Type Description
Decorated class or decorator.
Source code in magicclass\core.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def magicclass(
    class_: type | None = None,
    *,
    layout: str = "vertical",
    labels: bool = True,
    name: str = None,
    visible: bool | None = None,
    close_on_run: bool = None,
    popup_mode: PopUpModeStr | PopUpMode = None,
    error_mode: ErrorModeStr | ErrorMode = None,
    widget_type: WidgetTypeStr | WidgetType = WidgetType.none,
    icon: Any | None = None,
    stylesheet: str | StyleSheet = None,
    properties: dict[str, Any] = None,
    record: bool = True,
    symbol: str = "ui",
):
    """
    Decorator that can convert a Python class into a widget.

    >>> @magicclass
    >>> class C:
    >>>     ...
    >>> ui = C()
    >>> ui.show()  # open GUI

    Parameters
    ----------
    class_ : type, optional
        Class to be decorated.
    layout : str, "vertical" or "horizontal", default "vertical"
        Layout of the main widget.
    labels : bool, default True
        If true, magicgui labels are shown.
    name : str, optional
        Name of GUI.
    visible : bool, optional
        Initial visibility of GUI. Useful when magic class is nested.
    close_on_run : bool, default True
        If True, magicgui created by every method will be deleted after the method is
        completed without exceptions, i.e. magicgui is more like a dialog.
    popup_mode : str or PopUpMode, default PopUpMode.popup
        Option of how to popup FunctionGui widget when a button is clicked.
    error_mode : str or ErrorMode, default ErrorMode.msgbox
        Option of how to raise errors during function calls.
    widget_type : WidgetType or str, optional
        Widget type of container.
    icon : Any, optional
        Path to the icon image or any object that can be converted into an icon.
    stylesheet : str or StyleSheet object, optional
        Set stylesheet to the widget if given.
    properties : dict, optional
        Set properties to the widget if given. This argument is useful when you want
        to set width, height or margin without defining __post_init__.
    record : bool, default True
        If True, macro recording is enabled.
    symbol : str, default "ui"
        The identifier used in macro to represent this widget.

    Returns
    -------
    Decorated class or decorator.
    """
    if popup_mode is None:
        popup_mode = defaults["popup_mode"]
    if close_on_run is None:
        close_on_run = defaults["close_on_run"]
    if error_mode is None:
        error_mode = defaults["error_mode"]

    if isinstance(widget_type, str):
        widget_type = widget_type.lower()

    widget_type = WidgetType(widget_type)

    def wrapper(cls) -> type[ClassGui]:
        if not isinstance(cls, type):
            raise TypeError(f"magicclass can only wrap classes, not {type(cls)}")

        class_gui = _TYPE_MAP[widget_type]

        if not issubclass(cls, MagicTemplate):
            check_override(cls)

        # get class attributes first
        doc = cls.__doc__
        sig = inspect.signature(cls)
        annot = cls.__dict__.get("__annotations__", {})
        mod = cls.__module__
        qualname = cls.__qualname__

        new_attrs = convert_attributes(cls, hide=class_gui.__mro__, record=record)
        oldclass = type(cls.__name__ + _BASE_CLASS_SUFFIX, (cls,), {})
        newclass = type(cls.__name__, (class_gui, oldclass), new_attrs)

        newclass.__signature__ = sig
        newclass.__doc__ = doc
        newclass.__module__ = mod
        newclass.__qualname__ = qualname

        # concatenate annotations
        newclass.__annotations__ = class_gui.__annotations__.copy()
        newclass.__annotations__.update(annot)

        @functools_wraps(oldclass.__init__)
        def __init__(self: MagicTemplate, *args, **kwargs):
            # Without "app = " Jupyter freezes after closing the window!
            app = get_app()  # noqa: F841

            gui_kwargs = dict(
                layout=layout,
                labels=labels,
                name=name or cls.__name__,
                visible=visible,
                close_on_run=close_on_run,
                popup_mode=PopUpMode(popup_mode),
                error_mode=ErrorMode(error_mode),
            )

            # Inheriting Container's constructor is the most intuitive way.
            if kwargs and "__init__" not in cls.__dict__:
                gui_kwargs.update(kwargs)
                kwargs = {}

            class_gui.__init__(self, **gui_kwargs)

            with self.macro.blocked():
                super(oldclass, self).__init__(*args, **kwargs)

            self._convert_attributes_into_widgets()

            if widget_type in (WidgetType.collapsible, WidgetType.button):
                self.text = self.name

            if icon:
                self.icon = icon
            if stylesheet:
                self.native.setStyleSheet(str(stylesheet))
            if hasattr(self, "__post_init__"):
                with self.macro.blocked():
                    self.__post_init__()
            if properties:
                for k, v in properties.items():
                    setattr(self, k, v)
            self._my_symbol = _as_symbol(symbol)

        newclass.__init__ = __init__

        # Users may want to override repr
        newclass.__repr__ = oldclass.__repr__

        return newclass

    if class_ is None:
        return wrapper
    else:
        return wrapper(class_)

magiccontext(class_=None, *, into=None, close_on_run=None, popup_mode=None, error_mode=None, labels=True, name=None, icon=None, record=True)

Decorator that converts a Python class into a context menu.

Source code in magicclass\core.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
def magiccontext(
    class_: type = None,
    *,
    into: Callable | None = None,
    close_on_run: bool = None,
    popup_mode: str | PopUpMode = None,
    error_mode: str | ErrorMode = None,
    labels: bool = True,
    name: str | None = None,
    icon: Any | None = None,
    record: bool = True,
):
    """Decorator that converts a Python class into a context menu."""

    if popup_mode is None:
        popup_mode = defaults["popup_mode"]
    if close_on_run is None:
        close_on_run = defaults["close_on_run"]
    if error_mode is None:
        error_mode = defaults["error_mode"]

    if popup_mode in (
        PopUpMode.above,
        PopUpMode.below,
        PopUpMode.first,
        PopUpMode.last,
    ):
        raise ValueError(f"Mode {popup_mode.value} is not compatible with Menu.")

    def wrapper(cls) -> type[ContextMenuGui]:
        if not isinstance(cls, type):
            raise TypeError(f"magicclass can only wrap classes, not {type(cls)}")

        if not issubclass(cls, MagicTemplate):
            check_override(cls)

        # get class attributes first
        doc = cls.__doc__
        sig = inspect.signature(cls)
        mod = cls.__module__
        qualname = cls.__qualname__

        new_attrs = convert_attributes(cls, hide=ContextMenuGui.__mro__, record=record)
        oldclass = type(cls.__name__ + _BASE_CLASS_SUFFIX, (cls,), {})
        newclass = type(cls.__name__, (ContextMenuGui, oldclass), new_attrs)

        newclass.__signature__ = sig
        newclass.__doc__ = doc
        newclass.__module__ = mod
        newclass.__qualname__ = qualname

        @functools_wraps(oldclass.__init__)
        def __init__(self: MagicTemplate, *args, **kwargs):
            # Without "app = " Jupyter freezes after closing the window!
            app = get_app()  # noqa: F841

            gui_kwargs = dict(
                close_on_run=close_on_run,
                popup_mode=PopUpMode(popup_mode),
                error_mode=ErrorMode(error_mode),
                labels=labels,
                name=name or cls.__name__,
            )

            # Inheriting Container's constructor is the most intuitive way.
            if kwargs and "__init__" not in cls.__dict__:
                gui_kwargs.update(kwargs)
                kwargs = {}

            ContextMenuGui.__init__(self, **gui_kwargs)

            with self.macro.blocked():
                super(oldclass, self).__init__(*args, **kwargs)

            self._convert_attributes_into_widgets()

            if icon:
                self.icon = icon
            if hasattr(self, "__post_init__"):
                with self.macro.blocked():
                    self.__post_init__()
            if into is not None:
                from .signature import upgrade_signature

                upgrade_signature(into, additional_options={"context_menu": self})

        newclass.__init__ = __init__

        # Users may want to override repr
        newclass.__repr__ = oldclass.__repr__

        return newclass

    return wrapper if class_ is None else wrapper(class_)

magicmenu(class_=None, *, close_on_run=None, popup_mode=None, error_mode=None, labels=True, name=None, icon=None, record=True)

Decorator that converts a Python class into a menu bar.

Source code in magicclass\core.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def magicmenu(
    class_: type = None,
    *,
    close_on_run: bool = None,
    popup_mode: str | PopUpMode = None,
    error_mode: str | ErrorMode = None,
    labels: bool = True,
    name: str | None = None,
    icon: Any | None = None,
    record: bool = True,
):
    """Decorator that converts a Python class into a menu bar."""
    return _call_magicmenu(**locals(), menugui_class=MenuGui)

magictoolbar(class_=None, *, close_on_run=None, popup_mode=None, error_mode=None, labels=True, name=None, icon=None, record=True)

Decorator that converts a Python class into a menu bar.

Source code in magicclass\core.py
347
348
349
350
351
352
353
354
355
356
357
358
359
def magictoolbar(
    class_: type = None,
    *,
    close_on_run: bool = None,
    popup_mode: str | PopUpMode = None,
    error_mode: str | ErrorMode = None,
    labels: bool = True,
    name: str | None = None,
    icon: Any | None = None,
    record: bool = True,
):
    """Decorator that converts a Python class into a menu bar."""
    return _call_magicmenu(**locals(), menugui_class=ToolBarGui)

repeat(ui, index=-1)

Repeat last operation on GUI using recorded macro.

Parameters:

Name Type Description Default
ui MagicTemplate

Target magic-class widget.

required
index int

Which execution will be repeated. Any object that support list slicing can be used. By default the last operation will be repeated.

-1
Source code in magicclass\core.py
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
def repeat(ui: MagicTemplate, index: int = -1) -> None:
    """
    Repeat last operation on GUI using recorded macro.

    Parameters
    ----------
    ui : MagicTemplate
        Target magic-class widget.
    index : int, default -1
        Which execution will be repeated. Any object that support list slicing can be
        used. By default the last operation will be repeated.
    """
    warnings.warn(
        "repeat() is deprecated and will be removed soon. Use "
        "`ui.macro.repeat_method()` to specify call options in more detail.",
        DeprecationWarning,
    )
    line = ui.macro[index]
    try:
        line.eval({"ui": ui})
    except Exception as e:
        msg = e.args[0]
        msg = f"Caused by >>> {line}. {msg}"
        raise e
    return None

update_widget_state(ui, macro=None)

Update widget values based on a macro.

This helper function works similar to the update_widget method of FunctionGui. In most cases, this function will be used for restoring a state from a macro recorded before. Value changed signal will not be emitted within this operation.

Parameters:

Name Type Description Default
ui MagicTemplate

Magic class instance.

required
macro Macro or str

An executable macro or string that dictates how GUI will be updated.

None
Source code in magicclass\core.py
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
def update_widget_state(ui: MagicTemplate, macro: Macro | str | None = None) -> None:
    """
    Update widget values based on a macro.

    This helper function works similar to the `update_widget` method of `FunctionGui`.
    In most cases, this function will be used for restoring a state from a macro
    recorded before. Value changed signal will not be emitted within this operation.

    Parameters
    ----------
    ui : MagicTemplate
        Magic class instance.
    macro : Macro or str, optional
        An executable macro or string that dictates how GUI will be updated.
    """
    from macrokit import Head, Macro

    if macro is None:
        macro = ui.macro
    elif isinstance(macro, str):
        s = macro
        macro = Macro()
        for line in s.split("\n"):
            macro.append(line)
    elif not isinstance(macro, Macro):
        raise TypeError(
            f"The second argument must be a Macro or str, got {type(macro)}."
        )

    for expr in macro:
        if expr.head is Head.call:
            # ui.func(...)
            fname = str(expr.at(0, 1))
            args, kwargs = expr.eval_call_args()
            if fname.startswith("_"):
                if fname != "_call_with_return_callback":
                    continue
                fgui = get_function_gui(ui, args[0])

            else:
                fgui = get_function_gui(ui, fname)

            with fgui.changed.blocked():
                for key, value in kwargs.items():
                    getattr(fgui, key).value = value

        elif expr.head is Head.assign:
            # ui.field.value = ...
            # ui.vfield = ...
            expr.eval({}, {str(ui._my_symbol): ui})

    return None