Exemplo n.º 1
0
class SetTool(Tool):
    selection = T.Instance(Selection, allow_none=True)
    active = TypedTuple(T.Instance(BaseElement), kw={})
    css_classes = TypedTuple(T.Unicode())

    @T.default("css_classes")
    def _default_css_classes(self):
        return tuple([
            "active-set",
        ])

    @T.observe("active")
    def handler(self, change):

        try:
            new = set(change.new)
        except Exception:
            new = set()
        try:
            old = set(change.old)
        except Exception:
            old = set()

        exiting, entering = lifecycle(old, new)

        for el in entering:
            el.add_class(*self.css_classes)
        for el in exiting:
            el.remove_class(*self.css_classes)

    def add(self):
        self.active = tuple(set(self.active) | set(self.selection.elements()))

    def remove(self):
        self.active = tuple(set(self.active) - set(self.selection.elements()))

    def set_active(self):
        self.active = tuple(set(self.selection.elements()))

    @T.default("ui")
    def _default_ui(self):
        add_btn = W.Button(description="",
                           icon="plus",
                           layout={"width": "2.6em"})
        remove_btn = W.Button(description="",
                              icon="minus",
                              layout={"width": "2.6em"})
        set_btn = W.Button(description="",
                           icon="circle",
                           layout={"width": "2.6em"})

        add_btn.on_click(lambda *_: self.add())
        remove_btn.on_click(lambda *_: self.remove())
        set_btn.on_click(lambda *_: self.set_active())
        return W.HBox([add_btn, set_btn, remove_btn])
Exemplo n.º 2
0
class Pipe(W.Widget):
    disposition = T.Instance(PipeDisposition)
    enabled: bool = T.Bool(default_value=True)
    inlet: MarkElementWidget = T.Instance(MarkElementWidget, kw={})
    outlet: MarkElementWidget = T.Instance(MarkElementWidget, kw={})
    dirty: bool = T.Bool(default_value=True)
    observes: Tuple[str] = TypedTuple(T.Unicode(), kw={})
    reports: Tuple[str] = TypedTuple(T.Unicode(), kw={})
    _task: asyncio.Future = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def schedule_run(self, change: T.Bunch = None) -> asyncio.Task:
        # schedule task on loop
        if self._task:
            self._task.cancel()
        self._task = asyncio.create_task(self.run())

        self._task.add_done_callback(self._post_run)
        return self._task

    def _post_run(self, future: asyncio.Future):
        try:
            future.exception()
        except asyncio.CancelledError:
            pass
        except Exception as E:
            raise E

    async def run(self):
        # do work
        self.outlet.value = self.inlet.value

    def check_dirty(self) -> bool:
        flow = self.inlet.flow

        if any(
                any(re.match(f"^{obs}$", f) for f in flow)
                for obs in self.observes):
            # mark this pipe as dirty so will run
            self.dirty = True
            self.disposition = PipeDisposition.waiting
            # add this pipes reporting to the outlet flow
            flow = tuple(set([*flow, *self.reports]))
        else:
            self.dirty = False
            self.disposition = PipeDisposition.done
        self.outlet.flow = flow
        return self.dirty
Exemplo n.º 3
0
class MarkElementWidget(W.DOMWidget):
    value: Node = T.Instance(Node, allow_none=True).tag(sync=True,
                                                        **elk_serialization)
    index: MarkIndex = T.Instance(MarkIndex,
                                  kw={}).tag(sync=True,
                                             **W.widget_serialization)
    flow: Tuple[str] = TypedTuple(T.Unicode(), kw={}).tag(sync=True)

    def persist(self):
        if self.index.elements is None:
            self.build_index()
        else:
            self.index.elements.update(ElementIndex.from_els(self.value))
        return self

    def build_index(self) -> MarkIndex:
        if self.value is None:
            index = ElementIndex()
        else:
            with self.index.context:
                index = ElementIndex.from_els(self.value)
        self.index.elements = index
        return self.index

    def _ipython_display_(self, **kwargs):
        from IPython.display import JSON, display

        display(JSON(self.value.dict()))
Exemplo n.º 4
0
class Viewer(W.Widget):
    source: MarkElementWidget = T.Instance(
        MarkElementWidget, allow_none=True).tag(sync=True,
                                                **W.widget_serialization)

    selection: Selection = T.Instance(Selection,
                                      kw={}).tag(sync=True,
                                                 **W.widget_serialization)
    hover: Hover = T.Instance(Hover, kw={}).tag(sync=True,
                                                **W.widget_serialization)
    zoom = T.Instance(Zoom, kw={}).tag(sync=True, **W.widget_serialization)
    pan = T.Instance(Pan, kw={}).tag(sync=True, **W.widget_serialization)

    viewed: Tuple[str] = TypedTuple(trait=T.Unicode()).tag(
        sync=True)  # list element ids in the current view bounding box
    fit_tool: FitTool = T.Instance(FitTool)
    center_tool: CenterTool = T.Instance(CenterTool)

    @T.default("fit_tool")
    def _default_fit_tool(self) -> FitTool:
        return FitTool(handler=lambda _: self.fit())

    @T.default("center_tool")
    def _default_center_tool(self) -> CenterTool:
        return CenterTool(handler=lambda _: self.center())

    def fit(self):
        pass

    def center(self):
        pass
Exemplo n.º 5
0
class Menu(ReactWidget):
    _model_name = Unicode('MenuModel').tag(sync=True)
    description = Unicode(help="Menu item").tag(sync=True)
    items = TypedTuple(trait=Instance(MenuItem),
                       help="Menu items",
                       default=[],
                       allow_none=True).tag(
                           sync=True, **widget_serialization).tag(sync=True)
Exemplo n.º 6
0
class ToggleButtons(_Selection):
    """Group of toggle buttons that represent an enumeration.

    Only one toggle button can be toggled at any point in time.

    Parameters
    ----------
    {selection_params}

    tooltips: list
        Tooltip for each button. If specified, must be the
        same length as `options`.

    icons: list
        Icons to show on the buttons. This must be the name
        of a font-awesome icon. See `http://fontawesome.io/icons/`
        for a list of icons.

    button_style: str
        One of 'primary', 'success', 'info', 'warning' or
        'danger'. Applies a predefined style to every button.

    style: ToggleButtonsStyle
        Style parameters for the buttons.
    """
    _view_name = Unicode('ToggleButtonsView').tag(sync=True)
    _model_name = Unicode('ToggleButtonsModel').tag(sync=True)

    tooltips = TypedTuple(Unicode(),
                          help="Tooltips for each button.").tag(sync=True)
    icons = TypedTuple(
        Unicode(),
        help=
        "Icons names for each button (FontAwesome names without the fa- prefix)."
    ).tag(sync=True)
    style = InstanceDict(ToggleButtonsStyle).tag(sync=True,
                                                 **widget_serialization)

    button_style = CaselessStrEnum(
        values=['primary', 'success', 'info', 'warning', 'danger', ''],
        default_value='',
        allow_none=True,
        help="""Use a predefined styling for the buttons.""").tag(sync=True)
Exemplo n.º 7
0
class DatWidget(Widget):
    """TODO: Add docstring here
    """
    _model_name = Unicode('DatModel').tag(sync=True)
    _model_module = Unicode(module_name).tag(sync=True)
    _model_module_version = Unicode(module_version).tag(sync=True)

    targets = TypedTuple(WidgetTraitTuple(), allow_none=True).tag(sync=True)

    view = Instance(DOMWidget).tag(sync=True)
Exemplo n.º 8
0
class ElkJS(SyncedPipe):
    """Jupyterlab widget for calling `elkjs <https://github.com/kieler/elkjs>`_
    layout given a valid elkjson dictionary"""

    _model_name = T.Unicode("ELKLayoutModel").tag(sync=True)
    _model_module = T.Unicode(EXTENSION_NAME).tag(sync=True)
    _model_module_version = T.Unicode(EXTENSION_SPEC_VERSION).tag(sync=True)
    _view_module = T.Unicode(EXTENSION_NAME).tag(sync=True)

    observes = TypedTuple(T.Unicode(), default_value=(F.Anythinglayout, ))
    reports = TypedTuple(T.Unicode(), default_value=(F.Layout, ))

    async def run(self):
        # watch once
        if self.outlet is None:
            return

        # signal to browser and wait for done
        future_value = wait_for_change(self.outlet, "value")
        self.send({"action": "run"})

        # wait to return until
        await future_value
        self.outlet.persist()
Exemplo n.º 9
0
class Selection(Tool):
    ids = TypedTuple(trait=T.Unicode()).tag(
        sync=True)  # list element ids currently selected

    def get_index(self) -> MarkIndex:
        if self.tee is None:
            raise ValueError("Tool not attached to a pipe")
        if self.tee.inlet.index is None:
            # make the element index
            self.tee.inlet.build_index()
        return self.tee.inlet.index

    def elements(self) -> Iterator[BaseElement]:
        index = self.get_index()
        for el in map(index.from_id, self.ids):
            yield el
Exemplo n.º 10
0
class ReactWidget(widgets.DOMWidget, ClickMixin):
    """An example widget."""
    _view_name = Unicode('ReactView').tag(sync=True)
    _model_name = Unicode('ReactModel').tag(sync=True)
    _view_module = Unicode('ipyantd').tag(sync=True)
    _model_module = Unicode('ipyantd').tag(sync=True)
    _view_module_version = Unicode('^0.1.0').tag(sync=True)
    _model_module_version = Unicode('^0.1.0').tag(sync=True)
    child = Instance('ipywidgets.widgets.domwidget.DOMWidget', default_value=None, allow_none=True)\
                .tag(sync=True, **widget_serialization).tag(sync=True)
    children = TypedTuple(trait=Instance('ipywidgets.widgets.domwidget.DOMWidget'), help="children", default=[], allow_none=True)\
                .tag(sync=True, **widget_serialization).tag(sync=True)
    visible = CBool(True).tag(sync=True)
    content = Unicode(help="").tag(sync=True)
    style = Dict().tag(sync=True)
    class_name = Unicode('', help="class_name").tag(sync=True)
class BufferGeometry(BaseBufferGeometry):
    """BufferGeometry

    Autogenerated by generate-wrappers.js
    See https://threejs.org/docs/#api/core/BufferGeometry
    """
    def __init__(self, **kwargs):
        super(BufferGeometry, self).__init__(**kwargs)

    _model_name = Unicode('BufferGeometryModel').tag(sync=True)

    index = Union([
        Instance(BufferAttribute, allow_none=True),
        Instance(InterleavedBufferAttribute, allow_none=True)
    ]).tag(sync=True, **widget_serialization)

    attributes = Dict(
        Union([
            Instance(BufferAttribute),
            Instance(InterleavedBufferAttribute)
        ])).tag(sync=True, **widget_serialization)

    morphAttributes = Dict(
        TypedTuple(
            Union([
                Instance(BufferAttribute),
                Instance(InterleavedBufferAttribute)
            ]))).tag(sync=True, **widget_serialization)

    userData = Dict(default_value={}, allow_none=False).tag(sync=True)

    MaxIndex = CInt(65535, allow_none=False).tag(sync=True)

    _ref_geometry = Union([
        Instance(BaseGeometry, allow_none=True),
        Instance(BaseBufferGeometry, allow_none=True)
    ]).tag(sync=True, **widget_serialization)

    _store_ref = Bool(False, allow_none=False).tag(sync=True)

    type = Unicode("BufferGeometry", allow_none=False).tag(sync=True)
Exemplo n.º 12
0
class VisibilityPipe(Pipe):

    observes = TypedTuple(
        T.Unicode(),
        default_value=(
            F.AnyHidden,
            F.Layout,
        ),
    )

    @T.default("reports")
    def _default_reports(self):
        return (F.Layout, )

    async def run(self):
        if self.outlet is None or self.inlet is None:
            return

        root = self.inlet.index.root
        # generate an index of hidden elements
        vis_index = VisIndex.from_els(root)

        # clear old slack css classes from elements
        vis_index.clear_slack(root)

        # serialize the elements excluding hidden
        with exclude_hidden, exclude_layout:
            data = root.dict()

        # new root node with slack edges / ports introduced due to hidden
        # elements
        with Registry():
            value = convert_elkjson(data, vis_index)

            for el in index.iter_elements(value):
                el.id = el.get_id()
        self.outlet.value = value

        return self.outlet
Exemplo n.º 13
0
class Tool(W.Widget):
    tee: Pipe = T.Instance(Pipe, allow_none=True).tag(sync=True,
                                                      **W.widget_serialization)
    on_done = T.Any(allow_none=True)  # callback when done
    disable = T.Bool(default_value=False).tag(sync=True,
                                              **W.widget_serialization)
    reports = TypedTuple(T.Unicode(), kw={})
    _task: asyncio.Future = None
    ui = T.Instance(W.DOMWidget, allow_none=True)
    priority = T.Int(default_value=10)

    def handler(self, *args):
        """Handler callback for running the tool"""
        # canel old work if needed
        if self._task:
            self._task.cancel()

        # schedule work
        self._task = asyncio.create_task(self.run())

        # callback
        self._task.add_done_callback(self._finished)

        if self.tee:
            self.tee.inlet.flow = self.reports

    async def run(self):
        raise NotImplementedError()

    def _finished(self, future: asyncio.Future):
        try:
            future.result()
            if callable(self.on_done):
                self.on_done()
        except asyncio.CancelledError:
            pass  # cancellation should not log an error
        except Exception:
            self.log.exception(f"Error running tool: {type(self)}")
Exemplo n.º 14
0
class Geometry(BaseGeometry):
    """Geometry

    Autogenerated by generate-wrappers.js
    See https://threejs.org/docs/#api/core/Geometry
    """
    def __init__(self, **kwargs):
        super(Geometry, self).__init__(**kwargs)

    _model_name = Unicode('GeometryModel').tag(sync=True)

    vertices = List(trait=List()).tag(sync=True)

    colors = List(trait=Unicode(), default_value=["#ffffff"]).tag(sync=True)

    faces = TypedTuple(trait=Face3()).tag(sync=True)

    faceVertexUvs = List().tag(sync=True)

    lineDistances = List().tag(sync=True)

    morphTargets = List().tag(sync=True)

    morphNormals = List().tag(sync=True)

    skinWeights = List(trait=List()).tag(sync=True)

    skinIndices = List(trait=List()).tag(sync=True)

    _ref_geometry = Instance(BaseGeometry,
                             allow_none=True).tag(sync=True,
                                                  **widget_serialization)

    _store_ref = Bool(False, allow_none=False).tag(sync=True)

    type = Unicode("Geometry", allow_none=False).tag(sync=True)
Exemplo n.º 15
0
class _Selection(DescriptionWidget, ValueWidget, CoreWidget):
    """Base class for Selection widgets

    ``options`` can be specified as a list of values, list of (label, value)
    tuples, or a dict of {label: value}. The labels are the strings that will be
    displayed in the UI, representing the actual Python choices, and should be
    unique. If labels are not specified, they are generated from the values.

    When programmatically setting the value, a reverse lookup is performed
    among the options to check that the value is valid. The reverse lookup uses
    the equality operator by default, but another predicate may be provided via
    the ``equals`` keyword argument. For example, when dealing with numpy arrays,
    one may set equals=np.array_equal.
    """

    value = Any(None, help="Selected value", allow_none=True)
    label = Unicode(None, help="Selected label", allow_none=True)
    index = Int(None, help="Selected index", allow_none=True).tag(sync=True)

    options = Any(
        (),
        help=
        """Iterable of values, (label, value) pairs, or a mapping of {label: value} pairs that the user can select.

    The labels are the strings that will be displayed in the UI, representing the
    actual Python choices, and should be unique.
    """)

    _options_full = None

    # This being read-only means that it cannot be changed by the user.
    _options_labels = TypedTuple(
        trait=Unicode(), read_only=True,
        help="The labels for the options.").tag(sync=True)

    disabled = Bool(help="Enable or disable user changes").tag(sync=True)

    def __init__(self, *args, **kwargs):
        self.equals = kwargs.pop('equals', lambda x, y: x == y)
        # We have to make the basic options bookkeeping consistent
        # so we don't have errors the first time validators run
        self._initializing_traits_ = True
        options = _make_options(kwargs.get('options', ()))
        self._options_full = options
        self.set_trait('_options_labels', tuple(i[0] for i in options))
        self._options_values = tuple(i[1] for i in options)

        # Select the first item by default, if we can
        if 'index' not in kwargs and 'value' not in kwargs and 'label' not in kwargs:
            nonempty = (len(options) > 0)
            kwargs['index'] = 0 if nonempty else None
            kwargs['label'], kwargs['value'] = options[0] if nonempty else (
                None, None)

        super(_Selection, self).__init__(*args, **kwargs)
        self._initializing_traits_ = False

    @validate('options')
    def _validate_options(self, proposal):
        # if an iterator is provided, exhaust it
        if isinstance(proposal.value,
                      Iterable) and not isinstance(proposal.value, Mapping):
            proposal.value = tuple(proposal.value)
        # throws an error if there is a problem converting to full form
        self._options_full = _make_options(proposal.value)
        return proposal.value

    @observe('options')
    def _propagate_options(self, change):
        "Set the values and labels, and select the first option if we aren't initializing"
        options = self._options_full
        self.set_trait('_options_labels', tuple(i[0] for i in options))
        self._options_values = tuple(i[1] for i in options)
        if self._initializing_traits_ is not True:
            if len(options) > 0:
                if self.index == 0:
                    # Explicitly trigger the observers to pick up the new value and
                    # label. Just setting the value would not trigger the observers
                    # since traitlets thinks the value hasn't changed.
                    self._notify_trait('index', 0, 0)
                else:
                    self.index = 0
            else:
                self.index = None

    @validate('index')
    def _validate_index(self, proposal):
        if proposal.value is None or 0 <= proposal.value < len(
                self._options_labels):
            return proposal.value
        else:
            raise TraitError('Invalid selection: index out of bounds')

    @observe('index')
    def _propagate_index(self, change):
        "Propagate changes in index to the value and label properties"
        label = self._options_labels[
            change.new] if change.new is not None else None
        value = self._options_values[
            change.new] if change.new is not None else None
        if self.label is not label:
            self.label = label
        if self.value is not value:
            self.value = value

    @validate('value')
    def _validate_value(self, proposal):
        value = proposal.value
        try:
            return findvalue(self._options_values, value,
                             self.equals) if value is not None else None
        except ValueError:
            raise TraitError('Invalid selection: value not found')

    @observe('value')
    def _propagate_value(self, change):
        if change.new is None:
            index = None
        elif self.index is not None and self._options_values[
                self.index] == change.new:
            index = self.index
        else:
            index = self._options_values.index(change.new)
        if self.index != index:
            self.index = index

    @validate('label')
    def _validate_label(self, proposal):
        if (proposal.value is not None) and (proposal.value
                                             not in self._options_labels):
            raise TraitError('Invalid selection: label not found')
        return proposal.value

    @observe('label')
    def _propagate_label(self, change):
        if change.new is None:
            index = None
        elif self.index is not None and self._options_labels[
                self.index] == change.new:
            index = self.index
        else:
            index = self._options_labels.index(change.new)
        if self.index != index:
            self.index = index

    def _repr_keys(self):
        keys = super(_Selection, self)._repr_keys()
        # Include options manually, as it isn't marked as synced:
        for key in sorted(chain(keys, ('options', ))):
            if key == 'index' and self.index == 0:
                # Index 0 is default when there are options
                continue
            yield key
Exemplo n.º 16
0
 class TestCase(HasTraits):
     value = TypedTuple((1, 2, 3))
Exemplo n.º 17
0
 class TestCase(HasTraits):
     value = TypedTuple(Int())
Exemplo n.º 18
0
 class TestCase(HasTraits):
     value = TypedTuple(trait=Int(), default_value=(1, 2, 'foobar'))
Exemplo n.º 19
0
 class TestCase(HasTraits):
     value = TypedTuple(default_value=(1, 2, 3))
Exemplo n.º 20
0
class Pipe(W.Widget):
    enabled: bool = T.Bool(default_value=True)
    inlet: MarkElementWidget = T.Instance(MarkElementWidget, kw={})
    outlet: MarkElementWidget = T.Instance(MarkElementWidget, kw={})
    observes: Tuple[str] = TypedTuple(T.Unicode(), kw={})
    reports: Tuple[str] = TypedTuple(T.Unicode(), kw={})
    on_progress: Optional[Callable] = T.Any(allow_none=True)
    _task: asyncio.Future = None
    status: PipeStatus = T.Instance(PipeStatus, kw={})
    status_widget: W.DOMWidget = T.Instance(W.DOMWidget, allow_none=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    @T.default("status_widget")
    def _default_status_widget(self):
        widget = PipeStatusView()

        def update(change=None):
            widget.update(self)

        update()
        self.observe(update, "status")
        return widget

    def _ipython_display_(self, **kwargs):
        if self.status_widget is None:
            raise NotImplementedError()
        return self.status_widget._ipython_display_(**kwargs)

    def schedule_run(self, change: T.Bunch = None) -> asyncio.Task:
        # schedule task on loop
        if self._task:
            self._task.cancel()
        self._task = asyncio.create_task(self.run())

        self._task.add_done_callback(self._post_run)
        return self._task

    def _post_run(self, future: asyncio.Future):
        try:
            future.exception()
        except asyncio.CancelledError:
            pass
        except Exception as E:
            raise E

    async def run(self):
        # do work
        self.outlet.value = self.inlet.value

    def check_dirty(self) -> bool:
        flow = self.inlet.flow

        if any(
                any(re.match(f"^{obs}$", f) for f in flow)
                for obs in self.observes):
            # mark this pipe as dirty so will run
            self.status = PipeStatus.waiting()
            # add this pipes reporting to the outlet flow
            flow = tuple(set([*flow, *self.reports]))
        else:
            self.status = PipeStatus.finished()
        self.outlet.flow = flow
        return self.status.dirty()

    def status_update(
        self,
        status: PipeStatus,
        pipe: Optional["Pipe"] = None,
    ):
        if isinstance(pipe, Pipe):
            pipe.status_update(status=status)
        self.status = status

        if callable(self.on_progress):
            self.on_progress(self)

    def get_progress_value(self) -> float:
        return self.status.step()

    def error(self):
        if self.status.exception:
            raise self.status.exception
Exemplo n.º 21
0
class ValidationPipe(Pipe):

    observes = TypedTuple(T.Unicode(), default_value=(F.New, ))
    reports = TypedTuple(T.Unicode(), default_value=(F.Layout, ))
    fix_null_id = T.Bool(default_value=True)
    fix_edge_owners = T.Bool(default_value=True)
    fix_orphans = T.Bool(default_value=True)
    id_report = T.Instance(IDReport, kw={})
    edge_report = T.Instance(EdgeReport, kw={})
    schema_report = T.Dict(kw={})
    errors = T.Dict(kw={})

    async def run(self):
        index: MarkIndex = self.inlet.build_index()
        with index.context:
            self.get_reports(index)
            self.errors = self.collect_errors()
            if self.errors:
                raise ValueError("Inlet value is not valid")
            self.apply_fixes(index)

            value = self.inlet.index.root
            if value is self.outlet.value:
                # force refresh if same instance
                self.outlet._notify_trait("value", None, value)
            else:
                self.outlet.value = value
            self.get_reports(self.outlet.build_index())
            self.errors = self.collect_errors()
            if self.errors:
                raise ValueError("Outlet value is not valid")

    def get_reports(self, index: MarkIndex):
        self.edge_report = index.elements.check_edges()
        self.id_report = index.elements.check_ids(*self.edge_report.orphans)
        self.schema_report = {}  # TODO run elkjson schema validator

    def collect_errors(self) -> Dict:
        errors = {}
        if self.id_report.duplicated:
            errors["Nonunique Element Ids"] = self.id_report.duplicated

        if self.id_report.null_ids and not self.fix_null_id:
            errors["Null Id Elements"] = self.id_report.null_ids

        if self.edge_report.orphans and not self.fix_orphans:
            errors["Orphan Nodes"] = self.edge_report.orphans

        if self.edge_report.lca_mismatch and not self.fix_edge_owners:
            errors[
                "Lowest Common Ancestor Mismatch"] = self.edge_report.lca_mismatch

        if self.schema_report:
            errors["Schema Error"] = self.schema_report
        return errors

    def apply_fixes(self, index: MarkIndex):
        root = index.root
        if self.id_report.null_ids and self.fix_null_id:
            self.log.warning(f"fixing {len(self.id_report.null_ids)} ids")
            for el in self.id_report.null_ids:
                el.id = el.get_id()

        if self.edge_report.orphans and self.fix_orphans:
            for el in self.edge_report.orphans:
                root.add_child(el)

        if self.edge_report.lca_mismatch and self.fix_edge_owners:
            for edge, (old, new) in self.edge_report.lca_mismatch.items():
                old.edges.remove(edge)
                if new is None:
                    new = root
                new.edges.append(edge)
Exemplo n.º 22
0
class _MultipleSelection(DescriptionWidget, ValueWidget, CoreWidget):
    """Base class for multiple Selection widgets

    ``options`` can be specified as a list of values, list of (label, value)
    tuples, or a dict of {label: value}. The labels are the strings that will be
    displayed in the UI, representing the actual Python choices, and should be
    unique. If labels are not specified, they are generated from the values.

    When programmatically setting the value, a reverse lookup is performed
    among the options to check that the value is valid. The reverse lookup uses
    the equality operator by default, but another predicate may be provided via
    the ``equals`` keyword argument. For example, when dealing with numpy arrays,
    one may set equals=np.array_equal.
    """

    value = TypedTuple(trait=Any(), help="Selected values")
    label = TypedTuple(trait=Unicode(), help="Selected labels")
    index = TypedTuple(trait=Int(), help="Selected indices").tag(sync=True)

    options = Any(
        (),
        help=
        """Iterable of values, (label, value) pairs, or a mapping of {label: value} pairs that the user can select.

    The labels are the strings that will be displayed in the UI, representing the
    actual Python choices, and should be unique.
    """)
    _options_full = None

    # This being read-only means that it cannot be changed from the frontend!
    _options_labels = TypedTuple(
        trait=Unicode(), read_only=True,
        help="The labels for the options.").tag(sync=True)

    disabled = Bool(help="Enable or disable user changes").tag(sync=True)

    def __init__(self, *args, **kwargs):
        self.equals = kwargs.pop('equals', lambda x, y: x == y)

        # We have to make the basic options bookkeeping consistent
        # so we don't have errors the first time validators run
        self._initializing_traits_ = True
        options = _make_options(kwargs.get('options', ()))
        self._full_options = options
        self.set_trait('_options_labels', tuple(i[0] for i in options))
        self._options_values = tuple(i[1] for i in options)

        super(_MultipleSelection, self).__init__(*args, **kwargs)
        self._initializing_traits_ = False

    @validate('options')
    def _validate_options(self, proposal):
        if isinstance(proposal.value,
                      Iterable) and not isinstance(proposal.value, Mapping):
            proposal.value = tuple(proposal.value)
        # throws an error if there is a problem converting to full form
        self._options_full = _make_options(proposal.value)
        return proposal.value

    @observe('options')
    def _propagate_options(self, change):
        "Unselect any option"
        options = self._options_full
        self.set_trait('_options_labels', tuple(i[0] for i in options))
        self._options_values = tuple(i[1] for i in options)
        if self._initializing_traits_ is not True:
            self.index = ()

    @validate('index')
    def _validate_index(self, proposal):
        "Check the range of each proposed index."
        if all(0 <= i < len(self._options_labels) for i in proposal.value):
            return proposal.value
        else:
            raise TraitError('Invalid selection: index out of bounds')

    @observe('index')
    def _propagate_index(self, change):
        "Propagate changes in index to the value and label properties"
        label = tuple(self._options_labels[i] for i in change.new)
        value = tuple(self._options_values[i] for i in change.new)
        # we check equality so we can avoid validation if possible
        if self.label != label:
            self.label = label
        if self.value != value:
            self.value = value

    @validate('value')
    def _validate_value(self, proposal):
        "Replace all values with the actual objects in the options list"
        try:
            return tuple(
                findvalue(self._options_values, i, self.equals)
                for i in proposal.value)
        except ValueError:
            raise TraitError('Invalid selection: value not found')

    @observe('value')
    def _propagate_value(self, change):
        index = tuple(self._options_values.index(i) for i in change.new)
        if self.index != index:
            self.index = index

    @validate('label')
    def _validate_label(self, proposal):
        if any(i not in self._options_labels for i in proposal.value):
            raise TraitError('Invalid selection: label not found')
        return proposal.value

    @observe('label')
    def _propagate_label(self, change):
        index = tuple(self._options_labels.index(i) for i in change.new)
        if self.index != index:
            self.index = index

    def _repr_keys(self):
        keys = super(_MultipleSelection, self)._repr_keys()
        # Include options manually, as it isn't marked as synced:
        for key in sorted(chain(keys, ('options', ))):
            yield key
Exemplo n.º 23
0
class SysML2Client(trt.HasTraits):
    """
        A traitleted SysML v2 API Client.

    ..todo:
        - Add ability to use element download pagination.

    """

    model: Model = trt.Instance(Model, allow_none=True)

    host_url = trt.Unicode(default_value="http://localhost", )

    host_port = trt.Integer(
        default_value=9000,
        min=1,
        max=65535,
    )

    page_size = trt.Integer(
        default_value=5000,
        min=1,
    )

    paginate = trt.Bool(default_value=True)

    folder_path: Path = trt.Instance(Path, allow_none=True)
    json_files: Tuple[Path] = TypedTuple(trt.Instance(Path))
    json_file: Path = trt.Instance(Path, allow_none=True)

    selected_project: str = trt.Unicode(allow_none=True)
    selected_commit: str = trt.Unicode(allow_none=True)

    projects = trt.Dict()

    name_hints = trt.Dict()

    log_out: ipyw.Output = trt.Instance(ipyw.Output, args=())

    _next_url_regex = re.compile(r'<(http://.*)>; rel="next"')

    @trt.default("projects")
    def _make_projects(self):
        def process_project_safely(project) -> dict:
            # protect against projects that can't be parsed
            try:
                name_with_date = project["name"]
                name = " ".join(name_with_date.split()[:-6])
                timestamp = " ".join(name_with_date.split()[-6:])
                created = self._parse_timestamp(timestamp)
            except ValueError:
                # TODO: revise this when the API server changes the project name
                return dict()
            return dict(
                created=created,
                full_name=name_with_date,
                name=name,
            )

        try:
            projects = self._retrieve_data(self.projects_url)
        except Exception as exc:  # pylint: disable=broad-except
            warn(
                f"Could not retrieve projects from {self.projects_url}.\n{exc}"
            )
            return {}

        results = {
            project["@id"]: process_project_safely(project)
            for project in projects
        }

        return {
            project_id: project_data
            for project_id, project_data in results.items() if project_data
        }

    @trt.observe("host_url", "host_port")
    def _update_api_configuration(self, *_):
        self.projects = self._make_projects()

    @trt.observe("selected_commit")
    def _update_elements(self, *_, elements=None):
        if not (self.selected_commit or elements):
            return
        elements = elements or []
        self.model = Model.load(
            elements=elements,
            name=f"""{
                self.projects[self.selected_project]["name"]
            } ({self.host})""",
            source=self.elements_url,
        )
        for element in self.model.elements.values():
            if "label" not in element._derived:
                element._derived["label"] = get_label(element)

    @trt.observe("folder_path")
    def _update_json_files(self, *_):
        if self.folder_path.exists():
            self.json_files = tuple(self.folder_path.glob("*.json"))

    @trt.observe("json_file")
    def _update_elements_from_file(self, change: trt.Bunch = None):
        if change is None:
            return
        if change.new != change.old and change.new.exists():
            self.model = Model.load_from_file(self.json_file)

    @property
    def host(self):
        return f"{self.host_url}:{self.host_port}"

    @property
    def projects_url(self):
        return f"{self.host}/projects"

    @property
    def commits_url(self):
        return f"{self.projects_url}/{self.selected_project}/commits"

    @property
    def elements_url(self):
        if not self.paginate:
            warn("By default, disabling pagination still retrieves 100 "
                 "records at a time!  True pagination is not supported yet.")
        if not self.selected_project:
            raise SystemError("No selected project!")
        if not self.selected_commit:
            raise SystemError("No selected commit!")
        arguments = f"?page[size]={self.page_size}" if self.page_size else ""
        return f"{self.commits_url}/{self.selected_commit}/elements{arguments}"

    @lru_cache
    def _retrieve_data(self, url: str) -> List[Dict]:
        """Retrieve model data from a URL using pagination"""
        result = []
        while url:
            response = requests.get(url)

            if not response.ok:
                raise requests.HTTPError(
                    f"Failed to retrieve elements from '{url}', reason: {response.reason}"
                )

            result += response.json()

            link = response.headers.get("Link")
            if not link:
                break

            urls = self._next_url_regex.findall(link)
            url = None
            if len(urls) == 1:
                url = urls[0]
            elif len(urls) > 1:
                raise SystemError(
                    f"Found multiple 'next' pagination urls: {urls}")
        return result

    @staticmethod
    def _parse_timestamp(timestamp: str) -> datetime:
        if isinstance(timestamp, datetime):
            return timestamp
        return parser.parse(timestamp,
                            tzinfos=TIMEZONES).astimezone(timezone.utc)

    def _get_project_commits(self):
        def clean_fields(data: dict) -> dict:
            for key, value in tuple(data.items()):
                if not isinstance(key, str):
                    continue
                if key == "timestamp":
                    data[key] = self._parse_timestamp(value)
            return data

        commits = sorted(self._retrieve_data(self.commits_url),
                         key=lambda x: x["timestamp"])
        return {commit["@id"]: clean_fields(commit) for commit in commits}

    def _download_elements(self):
        elements = self._retrieve_data(self.elements_url)
        max_elements = self.page_size if self.paginate else 100
        if len(elements) == max_elements:
            warn("There are probably more elements that were not retrieved!")
        self._update_elements(elements=elements)

    def _load_from_file(self, file_path: Union[str, Path]):
        self.model = Model.load_from_file(file_path)