Source code for orangecanvas.registry.description

"""
Widget meta description classes
===============================

"""

import sys
import copy
import warnings

import typing
from typing import Union, Optional, List, Tuple, Iterable, Sequence

from orangecanvas.utils import qualified_name

__all__ = [
    "DescriptionError",
    "WidgetSpecificationError",
    "SignalSpecificationError",
    "CategorySpecificationError",
    "Single",
    "Multiple",
    "Default",
    "NonDefault",
    "Explicit",
    "Dynamic",
    "InputSignal",
    "OutputSignal",
    "WidgetDescription",
    "CategoryDescription",
]

# Exceptions


class DescriptionError(Exception):
    pass


class WidgetSpecificationError(DescriptionError):
    pass


class SignalSpecificationError(DescriptionError):
    pass


class CategorySpecificationError(DescriptionError):
    pass


###############
# Channel flags
###############

# A single signal
Single = 2

# Multiple signal (more then one input on the channel)
Multiple = 4

# Default signal (default or primary input/output)
Default = 8
NonDefault = 16

# Explicit - only connected if specifically requested or the only possibility
Explicit = 32

# Dynamic type output signal
Dynamic = 64


# Input/output signal (channel) description

if typing.TYPE_CHECKING:
    #: A simple single type spec (a fully qualified name or a type instance)
    TypeSpecSimple = Union[str, type]
    #: A tuple of simple type specs indicating a union type.
    TypeSpecUnion = Tuple[TypeSpecSimple, ...]
    #: Specification of a input/output type
    TypeSpec = Union[TypeSpecSimple, TypeSpecUnion]


[docs]class InputSignal(object): """ Description of an input channel. Parameters ---------- name : str Name of the channel. type : Union[str, type] or Tuple[Union[str, type]] Specify the type of the accepted input. This can be a `type` instance, a fully qualified type name or a tuple of such. If a tuple then the input type is a union of the passed types. .. versionchanged:: 0.1.5 Added `Union` type support. handler : str Name of the handler method for the signal. flags : int Channel flags. id : str, optional A unique id of the input signal. doc : str, optional A docstring documenting the channel. replaces : Iterable[str] A list of names this input replaces. """ name = "" # type: str type = "" # type: TypeSpec handler = "" # type: str id = None # type: Optional[str] doc = None # type: Optional[str] replaces = None # type: List[str] flags = None # type: int single = None # type: bool default = None # type: bool explicit = None # type: bool def __init__(self, name, type, handler, flags=Single + NonDefault, id=None, doc=None, replaces=()): # type: (str, TypeSpec, str, int, Optional[str], Optional[str], Iterable[str]) -> None self.name = name self.type = type self.handler = handler self.id = id self.doc = doc self.replaces = list(replaces) if not (flags & Single or flags & Multiple): flags += Single if not (flags & Default or flags & NonDefault): flags += NonDefault self.single = bool(flags & Single) self.default = bool(flags & Default) self.explicit = bool(flags & Explicit) self.flags = flags @property def types(self): # type: () -> Tuple[str, ...] """ The normalized type specification. This is a tuple of qualified type names that were passed to the constructor. .. versionadded:: 0.1.5 :type: Tuple[str, ...] """ return normalize_type(self.type) def __str__(self): fmt = ("{0.__name__}(name={name!r}, type={type!r}, " "handler={handler!r}, ...)") return fmt.format(type(self), **self.__dict__) __repr__ = __str__
def input_channel_from_args(args): if isinstance(args, tuple): return InputSignal(*args) elif isinstance(args, dict): return InputSignal(**args) elif isinstance(args, InputSignal): return copy.copy(args) else: raise TypeError("tuple, dict or InputSignal expected " "(got {0!r})".format(type(args)))
[docs]class OutputSignal(object): """ Description of an output channel. Parameters ---------- name : str Name of the channel. type : Union[str, type] or Tuple[Union[str, type]] Specify the type of the output. This can be a `type` instance, a fully qualified type name or a tuple of such. If a tuple then the output type is a union of the passed types. .. versionchanged:: 0.1.5 Added `Union` type support. flags : int, optional Channel flags. id : str A unique id of the output signal. doc : str, optional A docstring documenting the channel. replaces : List[str] A list of names this output replaces. """ name = "" # type: str type = "" # type: TypeSpec id = None # type: Optional[str] doc = None # type: Optional[str] replaces = None # type: List[str] single = None # type: bool default = None # type: bool explicit = None # type: bool dynamic = None # type: bool def __init__(self, name, type, flags=NonDefault, id=None, doc=None, replaces=()): # type: (str, TypeSpec, int, Optional[str], Optional[str], Iterable[str]) -> None self.name = name self.type = type self.id = id self.doc = doc self.replaces = list(replaces) if not (flags & Single or flags & Multiple): flags += Single if not (flags & Default or flags & NonDefault): flags += NonDefault self.default = bool(flags & Default) self.explicit = bool(flags & Explicit) self.dynamic = bool(flags & Dynamic) self.flags = flags @property def types(self): # type: () -> Tuple[str, ...] """ The normalized type specification. This is a tuple of qualified type names that were passed to the constructor. .. versionadded:: 0.1.5 :type: Tuple[str, ...] """ return normalize_type(self.type) def __str__(self): fmt = ("{0.__name__}(name={name!r}, type={type!r}, " "...)") return fmt.format(type(self), **self.__dict__) __repr__ = __str__
def output_channel_from_args(args): # type: (...) -> OutputSignal if isinstance(args, tuple): return OutputSignal(*args) elif isinstance(args, dict): return OutputSignal(**args) elif isinstance(args, OutputSignal): return copy.copy(args) else: raise TypeError("tuple, dict or OutputSignal expected " "(got {0!r})".format(type(args))) def normalize_type_simple(type_): # type: (TypeSpecSimple) -> str if isinstance(type_, type): return qualified_name(type_) elif isinstance(type_, str): return type_ else: raise TypeError def normalize_type(type_): # type: (TypeSpec) -> Tuple[str, ...] if isinstance(type_, (type, str)): return (normalize_type_simple(type_), ) else: return tuple(map(normalize_type_simple, type_))
[docs]class WidgetDescription(object): """ Description of a widget. Parameters ---------- name : str A human readable name of the widget. id : str A unique identifier of the widget (in most situations this should be the full module name). category : str, optional A name of the category in which this widget belongs. version : str, optional Version of the widget. By default the widget inherits the project version. description : str, optional A short description of the widget, suitable for a tool tip. long_description : str, optional A longer description of the widget, suitable for a 'what's this?' role. qualified_name : str A qualified name (import name) of the class implementing the widget. package : str, optional A package name where the widget is implemented. project_name : str, optional The distribution name that provides the widget. inputs : Sequence[InputSignal] A list of input channels provided by the widget. outputs : Sequence[OutputSignal] A list of output channels provided by the widget. help : str, optional URL or an Resource template of a detailed widget help page. help_ref : str, optional A text reference id that can be used to identify the help page, for instance an intersphinx reference. author : str, optional Author name. author_email : str, optional Author email address. maintainer : str, optional Maintainer name maintainer_email : str, optional Maintainer email address. keywords : list-of-str, optional A list of keyword phrases. priority : int, optional Widget priority (the order of the widgets in a GUI presentation). icon : str, optional A filename of the widget icon (in relation to the package). background : str, optional Widget's background color (in the canvas GUI). replaces : list of `str`, optional A list of ids this widget replaces (optional). short_name: str, optional Short name for display where text would otherwise elide. """ name = "" # type: str id = "" # type: str qualified_name = None # type: str short_name = None # type: str description = "" # type: str category = None # type: Optional[str] project_name = None # type: Optional[str] inputs = [] # type: Sequence[InputSignal] outputs = [] # type: Sequence[OutputSignal] replaces = [] # type: Sequence[str] keywords = [] # type: Sequence[str] def __init__(self, name, id, category=None, version=None, description=None, long_description=None, qualified_name=None, package=None, project_name=None, inputs=None, outputs=None, author=None, author_email=None, maintainer=None, maintainer_email=None, help=None, help_ref=None, url=None, keywords=None, priority=sys.maxsize, icon=None, background=None, replaces=None, short_name=None, ): if inputs is None: inputs = [] if outputs is None: outputs = [] if keywords is None: keywords = [] if replaces is None: replaces = [] if not qualified_name: # TODO: Should also check that the name is real. raise ValueError("'qualified_name' must be supplied.") self.name = name self.id = id self.category = category self.version = version self.description = description self.long_description = long_description self.qualified_name = qualified_name self.package = package self.project_name = project_name self.short_name = short_name # Copy input/outputs and normalize the type to string. inputs = [ InputSignal( i.name, normalize_type(i.type), i.handler, i.flags, i.id, i.doc, i.replaces ) for i in inputs ] outputs = [ OutputSignal( o.name, normalize_type(o.type), o.flags, o.id, o.doc, o.replaces ) for o in outputs ] self.inputs = inputs self.outputs = outputs self.help = help self.help_ref = help_ref self.author = author self.author_email = author_email self.maintainer = maintainer self.maintainer_email = maintainer_email self.url = url self.keywords = keywords self.priority = priority self.icon = icon self.background = background self.replaces = list(replaces) def __str__(self): return ("WidgetDescription(name=%(name)r, id=%(id)r), " "category=%(category)r, ...)") % self.__dict__ def __repr__(self): return self.__str__() @classmethod def from_module(cls, module): warnings.warn( "'WidgetDescription.from_module' is deprecated", PendingDeprecationWarning, stacklevel=2 ) from .utils import widget_from_module_globals return widget_from_module_globals(module)
[docs]class CategoryDescription(object): """ Description of a widget category. Parameters ---------- name : str A human readable name. version : str, optional Version string. description : str, optional A short description of the category, suitable for a tool tip. long_description : str, optional A longer description. qualified_name : str, Qualified name project_name : str A project name providing the category. priority : int Priority (order in the GUI). icon : str An icon filename (a resource name retrievable using `pkgutil.get_data` relative to `qualified_name`). background : str An background color for widgets in this category. hidden : bool Is this category (by default) hidden in the canvas gui. """ name = "" # type: str qualified_name = "" # type: str project_name = "" # type: str priority = None # type: int icon = "" # type: str def __init__(self, name=None, version=None, description=None, long_description=None, qualified_name=None, package=None, project_name=None, author=None, author_email=None, maintainer=None, maintainer_email=None, url=None, help=None, keywords=None, widgets=None, priority=sys.maxsize, icon=None, background=None, hidden=False ): self.name = name self.version = version self.description = description self.long_description = long_description self.qualified_name = qualified_name self.package = package self.project_name = project_name self.author = author self.author_email = author_email self.maintainer = maintainer self.maintainer_email = maintainer_email self.url = url self.help = help self.keywords = keywords self.widgets = widgets or [] self.priority = priority self.icon = icon self.background = background self.hidden = hidden def __str__(self): return "CategoryDescription(name=%(name)r, ...)" % self.__dict__ def __repr__(self): return self.__str__() @classmethod def from_package(cls, package): warnings.warn( "'CategoryDescription.from_package' is deprecated", DeprecationWarning, stacklevel=2 ) from .utils import category_from_package_globals return category_from_package_globals(package)