from __future__ import annotations
import inspect
from dataclasses import _FIELDS
from functools import wraps
from typing import Callable, Any, TYPE_CHECKING
from docstring_parser import parse
from qtpy.QtWidgets import QApplication, QMessageBox
if TYPE_CHECKING:
from magicgui.widgets._bases import Widget
from magicgui.widgets import FunctionGui
from .fields import MagicField
APPLICATION = None
[docs]def iter_members(cls: type, exclude_prefix: str = "__") -> list[str, Any]:
"""
Iterate over all the members in the order of source code line number.
"""
members = getmembers(cls, exclude_prefix)
fields: dict[str, MagicField] = getattr(cls, _FIELDS, {})
return members + list(fields.items())
[docs]def check_collision(cls0: type, cls1: type):
"""
Check if two classes have name collisions.
"""
mem0 = set(x[0] for x in iter_members(cls0))
mem1 = set(x[0] for x in iter_members(cls1))
collision = mem0 & mem1
if collision:
raise AttributeError(f"Collision between {cls0.__name__} and {cls1.__name__}: {collision}")
[docs]def getmembers(object: type, exclude_prefix: str):
"""
This function is identical to inspect.getmembers except for the order
of the results. We have to sort the name in the order of line number.
"""
mro = (object,) + inspect.getmro(object)
results = []
processed = set()
names: list[str] = list(object.__dict__.keys())
try:
for base in mro:
for k in base.__dict__.keys():
if k not in names:
names.append(k)
except AttributeError:
pass
for key in names:
try:
value = getattr(object, key)
# handle the duplicate key
if key in processed:
raise AttributeError
except AttributeError:
for base in mro:
if key in base.__dict__:
value = base.__dict__[key]
break
else:
continue
if not key.startswith(exclude_prefix):
results.append((key, value))
processed.add(key)
return results
[docs]def n_parameters(func: Callable):
"""
Count the number of parameters of a callable object.
"""
return len(inspect.signature(func).parameters)
[docs]def raise_error_in_msgbox(_func: Callable, parent: Widget = None):
"""
If exception happened inside function, then open a message box.
"""
@wraps(_func)
def wrapped_func(*args, **kwargs):
try:
out = _func(*args, **kwargs)
except Exception as e:
QMessageBox.critical(parent.native, e.__class__.__name__, str(e), QMessageBox.Ok)
out = e
return out
return wrapped_func
[docs]def identity_wrapper(_func: Callable, parent: Widget = None):
"""
Do nothing.
"""
@wraps(_func)
def wrapped_func(*args, **kwargs):
return _func(*args, **kwargs)
return wrapped_func
[docs]def get_parameters(fgui: FunctionGui):
return {k: v.default for k, v in fgui.__signature__.parameters.items()}
[docs]def get_signature(func):
if hasattr(func, "__signature__"):
sig = func.__signature__
else:
sig = inspect.signature(func)
return sig
[docs]def define_callback(self, callback: Callable):
clsname, funcname = callback.__qualname__.split(".")
def _callback():
# search for parent instances that have the same name.
current_self = self
while not (hasattr(current_self, funcname) and
current_self.__class__.__name__ == clsname):
current_self = current_self.__magicclass_parent__
getattr(current_self, funcname)()
return None
return _callback
[docs]def gui_qt():
"""
Call "%gui qt" magic,
"""
try:
from IPython import get_ipython
except ImportError:
get_ipython = lambda: False
shell = get_ipython()
if shell and shell.active_eventloop != "qt":
shell.enable_gui("qt")
return None
[docs]def get_app():
"""
Get QApplication. This is important when using Jupyter.
"""
gui_qt()
app = QApplication.instance()
if app is None:
app = QApplication([])
global APPLICATION
APPLICATION = app
return app
[docs]def screen_center():
"""
Get the center coordinate of the screen.
"""
return QApplication.desktop().screen().rect().center()
[docs]class InvalidMagicClassError(Exception):
"""
This exception will be raised when class definition is not a valid magic-class.
"""