Skip to content

Commit

Permalink
Merge pull request #11 from ales-erjavec/union-types
Browse files Browse the repository at this point in the history
[ENH] Allow specification of union types in input/output meta defintions
  • Loading branch information
janezd authored Aug 30, 2019
2 parents 3a7d5b3 + b0a8058 commit b1a7b7b
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 66 deletions.
10 changes: 6 additions & 4 deletions orangecanvas/document/editlinksdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ..registry import InputSignal, OutputSignal

from ..resources import icon_loader
from ..utils import type_str

if typing.TYPE_CHECKING:
from ..scheme import SchemeNode
Expand All @@ -42,9 +43,9 @@ class EditLinksDialog(QDialog):
A dialog for editing links.
>>> dlg = EditLinksDialog()
>>> dlg.setNodes(file_node, test_learners_node)
>>> dlg.setLinks([(file_node.output_channel("Data"),
... test_learners_node.input_channel("Data")])
>>> dlg.setNodes(source_node, sink_node)
>>> dlg.setLinks([(source_node.output_channel("Data"),
... sink_node.input_channel("Data"))])
>>> if dlg.exec_() == EditLinksDialog.Accepted:
... new_links = dlg.links()
...
Expand Down Expand Up @@ -662,7 +663,8 @@ def setSchemeNode(self, node):
text_item.setHtml(text)
text_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
text_item.setToolTip(
escape(getattr(channel, 'description', channel.type)))
escape(getattr(channel, 'description', type_str(channel.type)))
)

grid.addItem(text_item, i, label_row,
alignment=label_alignment)
Expand Down
2 changes: 1 addition & 1 deletion orangecanvas/registry/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
log = logging.getLogger(__name__)

# Registry hex version
VERSION_HEX = 0x000104
VERSION_HEX = 0x000105


class WidgetRegistry(object):
Expand Down
65 changes: 56 additions & 9 deletions orangecanvas/registry/description.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import warnings

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

from orangecanvas.utils import qualified_name

Expand Down Expand Up @@ -72,10 +72,13 @@ class CategorySpecificationError(DescriptionError):

# 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[type, str]
TypeSpec = Union[TypeSpecSimple, TypeSpecUnion]


class InputSignal(object):
Expand All @@ -86,8 +89,13 @@ class InputSignal(object):
----------
name : str
Name of the channel.
type : str or `type`
Type of the accepted signals.
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
Expand Down Expand Up @@ -132,6 +140,19 @@ def __init__(self, name, type, handler, flags=Single + NonDefault,
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}, ...)")
Expand Down Expand Up @@ -160,8 +181,13 @@ class OutputSignal(object):
----------
name : str
Name of the channel.
type : str or `type`
Type of the output signals.
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
Expand Down Expand Up @@ -209,6 +235,19 @@ def __init__(self, name, type, flags=Single + NonDefault,
"Output signal can not be 'Multiple' and 'Dynamic'."
)

@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}, "
"...)")
Expand All @@ -230,8 +269,8 @@ def output_channel_from_args(args):
"(got {0!r})".format(type(args)))


def normalize_type(type_):
# type: (TypeSpec) -> str
def normalize_type_simple(type_):
# type: (TypeSpecSimple) -> str
if isinstance(type_, type):
return qualified_name(type_)
elif isinstance(type_, str):
Expand All @@ -240,6 +279,14 @@ def normalize_type(type_):
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_))


class WidgetDescription(object):
"""
Description of a widget.
Expand Down
14 changes: 3 additions & 11 deletions orangecanvas/registry/qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from AnyQt.QtCore import QObject, Qt
from AnyQt.QtCore import pyqtSignal as Signal

from ..utils import type_str
from .discovery import WidgetDiscovery
from .description import WidgetDescription, CategoryDescription
from .base import WidgetRegistry
Expand Down Expand Up @@ -309,26 +310,17 @@ def tooltip_helper(desc):

inputs_fmt = "<li>{name} ({class_name})</li>"

def type_str(type_name):
# type: (Union[str, type]) -> str
if isinstance(type_name, type):
return type_str("{0.__module__}.{0.__qualname__}".format(type_name))
elif type_name.startswith("builtins."):
return type_name[len("builtins."):]
else:
return type_name

if desc.inputs:
inputs = "".join(inputs_fmt.format(name=inp.name,
class_name=type_str(inp.type))
class_name=type_str(inp.types))
for inp in desc.inputs)
tooltip.append("Inputs:<ul>{0}</ul>".format(inputs))
else:
tooltip.append("No inputs")

if desc.outputs:
outputs = "".join(inputs_fmt.format(name=out.name,
class_name=type_str(out.type))
class_name=type_str(out.types))
for out in desc.outputs)
tooltip.append("Outputs:<ul>{0}</ul>".format(outputs))
else:
Expand Down
22 changes: 22 additions & 0 deletions orangecanvas/registry/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from operator import attrgetter

import unittest
from orangecanvas.registry import InputSignal, OutputSignal

from ..base import WidgetRegistry
from .. import description
Expand Down Expand Up @@ -97,3 +98,24 @@ def test_registry_const(self):
for desc in [one_desc, zero_desc, sub_desc,
add_desc])
)

def test_input_signal(self):
isig_1 = InputSignal("A", str, "aa", id="sig-a")
isig_2 = InputSignal("A", 'builtins.str', "aa", id="sig-a")
self.assertTupleEqual(isig_1.types, isig_2.types)
self.assertTupleEqual(isig_1.types, ('builtins.str',))
isig_1 = InputSignal("A", (str, int), "aa", id="sig-a")
isig_2 = InputSignal("A", ('builtins.str', "builtins.int",), "aa",
id="sig-a")
self.assertTupleEqual(isig_1.types, isig_2.types)

def test_output_signal(self):
osig_1 = OutputSignal("A", str, id="sig-a")
osig_2 = OutputSignal("A", 'builtins.str', id="sig-a")
self.assertTupleEqual(osig_1.types, osig_2.types)
self.assertTupleEqual(osig_1.types, ('builtins.str',))
osig_1 = OutputSignal("A", (str, int), id="sig-a")
osig_2 = OutputSignal("A", ('builtins.str', "builtins.int",),
id="sig-a")
self.assertTupleEqual(osig_1.types, osig_2.types)
self.assertTupleEqual(osig_1.types, ('builtins.str', "builtins.int",))
Loading

0 comments on commit b1a7b7b

Please sign in to comment.