Skip to content

components

WHTML

Magic class that takes html text and creates an object that is rendered with that text.

ipywidget.HTML widgets do not get their links XSRF protected, but obj._repr_html_ does!

Warnings

This HTML is not escaped, so could do bad stuff!

Source code in cassini/ext/ipygui/components.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
class WHTML:
    """
    Magic class that takes html text and creates an object that is rendered with that text.

    `ipywidget.HTML` widgets do not get their links XSRF protected, but `obj._repr_html_` does!

    Warnings
    --------
    This HTML is not escaped, so could do bad stuff!
    """

    def __init__(self, html: str):
        self.html = html

    def _repr_html_(self) -> str:
        return self.html

UnescapedDataFrame

Bases: DataFrame

Subclass of pd.DataFrame that slightly dangerously does not escape any html in its self._repr_html_ method.

Warnings

This class deliberately does not escape HTML for objects it contains. This means you need to trust these objects.

Source code in cassini/ext/ipygui/components.py
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
class UnescapedDataFrame(pd.DataFrame):
    """
    Subclass of `pd.DataFrame` that slightly dangerously does not escape any html in its `self._repr_html_` method.

    Warnings
    --------
    This class deliberately does not escape HTML for objects it contains. This means you need to trust these objects.
    """

    @staticmethod
    def try_html_repr(obj: object) -> str:
        """
        Get text form of obj, first try `obj._repr_html_`, fallback on `str(obj)`.

        Warnings
        --------
        This method does not escape `obj._repr_html_`. So make sure your reprs are trustworthy!
        """
        if hasattr(obj, "_repr_html_"):
            return cast(str, obj._repr_html_())
        return html.escape(str(obj))

    @property
    def _constructor(self) -> Type["UnescapedDataFrame"]:
        return UnescapedDataFrame

    def _repr_html_(self) -> str:
        formatters: Mapping[Hashable, Callable[[object], str]] = {
            name: self.try_html_repr
            for name, dtype in self.dtypes.items()
            if pd_types.is_object_dtype(dtype)
        }
        return self.to_html(escape=False, formatters=formatters)

try_html_repr staticmethod

try_html_repr(obj)

Get text form of obj, first try obj._repr_html_, fallback on str(obj).

Warnings

This method does not escape obj._repr_html_. So make sure your reprs are trustworthy!

Source code in cassini/ext/ipygui/components.py
145
146
147
148
149
150
151
152
153
154
155
156
@staticmethod
def try_html_repr(obj: object) -> str:
    """
    Get text form of obj, first try `obj._repr_html_`, fallback on `str(obj)`.

    Warnings
    --------
    This method does not escape `obj._repr_html_`. So make sure your reprs are trustworthy!
    """
    if hasattr(obj, "_repr_html_"):
        return cast(str, obj._repr_html_())
    return html.escape(str(obj))

InputSequence

Helper class for creating 'input sequences' i.e. a sequency of widgets that you fill in, and then click confirm.

Parameters:

Name Type Description Default
done_action callable

function to call when confirm is clicked. self is passed as first argument, then subsequently each child (see next parameter!) is passed to done_action.

required
*children ipywidget

widgets to display in order. When confirm clicked, these widget objects are passed to done_action.

()
Source code in cassini/ext/ipygui/components.py
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
class InputSequence:
    """
    Helper class for creating 'input sequences' i.e. a sequency of widgets that you fill in, and then click confirm.

    Parameters
    ----------
    done_action : callable
        function to call when confirm is clicked. `self` is passed as first argument, then subsequently each child (see next parameter!) is passed to done_action.
    *children : ipywidget
        widgets to display in order. When confirm clicked, these widget objects are passed to done_action.
    """

    stored_traits = ["value", "disabled"]

    def __init__(self, done_action: Callable[..., Any], *children: DOMWidget):
        self.done_action = done_action
        self.children = children
        self.initial_traits = {}

        self.confirm = Button(description="Confirm", disabled=True)
        self.new = Button(description="New")
        self.status = Output()

        self.output = Output(layout=Layout(width="auto"))

        self.confirm.on_click(self._do_done)
        self.new.on_click(self.reset)

        for child in self.children:
            child.layout.width = "auto"
            child.style.description_width = "10%"
            self.initial_traits[child] = {
                k: v for k, v in child.get_state().items() if k in self.stored_traits
            }
            child.observe(self.child_updated, "value")

    def reset(self, change: Any) -> None:
        """
        Reset the state of the InputSequence. Called when `new` button clicked.
        """
        for child in self.children:
            child.open()
            traits = self.initial_traits[child]
            for k, v in traits.items():
                setattr(child, k, v)

        self.confirm.disabled = True

    def child_updated(self, change: Any) -> None:
        """
        Called when a child is updated, meant to stop you pressing confirm until you've entered something into every
        widget... don't think it actually works tho!
        """
        if all(map(lambda val: val is not None, self.children)):
            self.confirm.disabled = False

    def as_widget(self) -> DOMWidget:
        """
        Build `self` into `ipywidget`

        Returns
        -------
        widget : DOMWidget
            widget form of self.
        """
        return VBox((*self.children, HBox((self.confirm, self.new)), self.status))

    def display(self) -> Output:
        """
        Displays `self.as_widget()`.

        Returns
        -------
        output : Output
            output widget which we displayed into...
        """
        self.output.append_display_data(self.as_widget())
        return self.output

    def _do_done(self, pressed: Any) -> None:
        """
        Internal function called when confirm clicked.
        """
        values = []

        self.confirm.disabled = True

        for child in self.children:
            child.disabled = True
            values.append(child.value)

        with self.output:
            self.done_action(self, *values)

reset

reset(change)

Reset the state of the InputSequence. Called when new button clicked.

Source code in cassini/ext/ipygui/components.py
207
208
209
210
211
212
213
214
215
216
217
def reset(self, change: Any) -> None:
    """
    Reset the state of the InputSequence. Called when `new` button clicked.
    """
    for child in self.children:
        child.open()
        traits = self.initial_traits[child]
        for k, v in traits.items():
            setattr(child, k, v)

    self.confirm.disabled = True

child_updated

child_updated(change)

Called when a child is updated, meant to stop you pressing confirm until you've entered something into every widget... don't think it actually works tho!

Source code in cassini/ext/ipygui/components.py
219
220
221
222
223
224
225
def child_updated(self, change: Any) -> None:
    """
    Called when a child is updated, meant to stop you pressing confirm until you've entered something into every
    widget... don't think it actually works tho!
    """
    if all(map(lambda val: val is not None, self.children)):
        self.confirm.disabled = False

as_widget

as_widget()

Build self into ipywidget

Returns:

Name Type Description
widget DOMWidget

widget form of self.

Source code in cassini/ext/ipygui/components.py
227
228
229
230
231
232
233
234
235
236
def as_widget(self) -> DOMWidget:
    """
    Build `self` into `ipywidget`

    Returns
    -------
    widget : DOMWidget
        widget form of self.
    """
    return VBox((*self.children, HBox((self.confirm, self.new)), self.status))

display

display()

Displays self.as_widget().

Returns:

Name Type Description
output Output

output widget which we displayed into...

Source code in cassini/ext/ipygui/components.py
238
239
240
241
242
243
244
245
246
247
248
def display(self) -> Output:
    """
    Displays `self.as_widget()`.

    Returns
    -------
    output : Output
        output widget which we displayed into...
    """
    self.output.append_display_data(self.as_widget())
    return self.output

BaseTierGui

Bases: Generic[T]

Mixin to provide nice notebook outputs for Jupyter Notebooks.

Source code in cassini/ext/ipygui/components.py
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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
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
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
class BaseTierGui(Generic[T]):
    """
    Mixin to provide nice notebook outputs for Jupyter Notebooks.
    """

    def __init__(self, tier: T):
        self.tier = tier

    def _get_header_components(self) -> Dict[str, Callable[[], DOMWidget]]:
        """
        Creates the dictionary that forms the components of the header.

        Returns
        -------
        components : dict
            Dictionary of callables in form (key: value) = (header, callable), where the callable creates a returns a
            widget.

        Notes
        -----
        The order of the elements dictates what order they're rendered so insert them in the order you want!

        Overload this method to customise the appearance of your `Tier` header.
        """
        components: Dict[str, Callable[[], Any]] = dict()
        components["Description"] = self._build_description
        components["Highlights"] = self._build_highlights_accordion
        components["Conclusion"] = self._build_conclusion

        child = self.tier.child_cls
        if child:
            child_name = child.pretty_type
            components[f"{child_name}s"] = self._build_children
            components[f"New {child_name}"] = self.new_child

        return components

    def _build_header_title(self) -> DOMWidget:
        """
        Creates a widget that displays the title a `Tier` along side clickable links and buttons to its components
        """
        open_btn = Button(description="Folder")
        open_btn.on_click(lambda cb: self.tier.open_folder())
        title = f'<h1 style="display: inline;"><a href="{self.tier.href}">{self.tier.name}</a></h1>'
        parts = []
        parent = self.tier.parent
        while parent:
            parts.append(f'<a href="{parent.href}" target="_blank">{parent.name}</a>')
            parent = parent.parent

        text = title + '<h3 style="display: inline;">'

        if parts:
            text += f"({'->'.join(parts[::-1])})"

        text += "</h3>"
        return VBox((widgetify_html(text), open_btn))

    def _build_description(self) -> Union[DOMWidget, None]:
        """
        Creates a widget that displays the motivation for a `Tier`.
        """
        if isinstance(self.tier, NotebookTierBase):
            description = self.tier.description
            return widgetify(Markdown(description))
        else:
            return None

    def _build_highlights_accordion(self) -> Union[DOMWidget, None]:
        """
        Creates a widget that displays highlights for this `Tier` in an `ipywidgets.Accordion` - which is nice!
        """
        if not isinstance(self.tier, NotebookTierBase):
            return None

        highlights = self.tier.get_highlights()

        if not highlights:
            return None

        widget = Accordion()

        for i, (name, highlight) in enumerate(highlights.items()):
            out = Output()
            widget.children = (*widget.children, out)
            with out:
                for item in highlight:
                    publish_display_data(**item)
            widget.set_title(i, name)
        widget.selected_index = None
        return widget

    def _build_conclusion(self) -> Union[DOMWidget, None]:
        """
        Build widget to display conclusion of this `Tier` object.
        """
        if isinstance(self.tier, NotebookTierBase):
            conclusion = self.tier.conclusion
            return widgetify(Markdown(conclusion))
        else:
            return None

    def _build_children(self) -> DOMWidget:
        """
        Build a widget to display an `UnescapedDataFrame` containing this `Tier`'s children.
        """
        children_df = create_children_df(self.tier)
        return widgetify(children_df)

    def header(
        self,
        *,
        include: Union[List[str], None] = None,
        exclude: Union[List[str], None] = None,
    ) -> DOMWidget:
        """
        Builds header widget from its components.

        Parameters
        ----------
        include : Sequence[str]
            names of components to render in header widget.
        exclude : Sequence[str]
            names of components not to render in header widget.

        Notes
        -----
        Parameters include and exclude are mutually exclusive.

        Returns
        -------
        header : DOMWidget
            Widget that can be displayed containing all the components of this `Tier`'s header.
        """
        if include and exclude:
            raise ValueError("Only one of include or exclude can be provided")

        component_makers = self._get_header_components()

        if include:
            component_makers = {
                k: v for k, v in component_makers.items() if k in include
            }

        if exclude:
            component_makers = {
                k: v for k, v in component_makers.items() if k not in exclude
            }

        components = [self._build_header_title()]

        for name, component_builder in component_makers.items():
            component = component_builder()
            if component:
                components.append(widgetify_html(f"<h3>{name}</h3>"))
                components.append(component)

        return VBox(components)

    def display_highlights(self) -> None:
        """
        Display an `ipywidgets.Accordian` with this `Tier`'s highlights.
        """
        display(self._build_highlights_accordion())

    def children_df(
        self,
        *,
        include: Union[List[str], None] = None,
        exclude: Union[List[str], None] = None,
    ) -> Union[UnescapedDataFrame, None]:
        """
        Calls `tier.children_df` but returns an `UnescapedDataFrame` instead.
        """
        children_df = create_children_df(self.tier, include=include, exclude=exclude)
        return UnescapedDataFrame(children_df)

    def new_child(self) -> DOMWidget:
        """
        Widget for creating new child for this `Tier`.
        """
        child_cls = self.tier.child_cls

        if not child_cls:
            return

        if not issubclass(child_cls, NotebookTierBase):

            def create(form: InputSequence, name: str) -> None:
                with form.status:
                    obj = child_cls(
                        *self.tier.identifiers, name, project=self.tier.project
                    )
                    obj.setup_files()
                    display(widgetify_html(obj._repr_html_()))

            form = InputSequence(
                create, Text(description="Identifier", placeholder="{child.id_regex}")
            )

            return form.as_widget()
        else:
            options = child_cls.get_templates(self.tier.project)
            mapping = {path.name: path for path in options}
            selection = Select(options=mapping.keys(), description="Template")

            def create_notebook_child(
                form: InputSequence, name: str, template: str, description: str
            ) -> None:
                with form.status:
                    obj: NotebookTierBase = child_cls(
                        *self.tier.identifiers, name, project=self.tier.project
                    )
                    obj.setup_files(mapping[template])
                    obj.description = description
                    display(widgetify_html(obj._repr_html_()))

            form = InputSequence(
                create_notebook_child,
                Text(description="Identifier", placeholder="{child.id_regex}"),
                selection,
                Textarea(description="Motivation"),
            )

            return form.as_widget()

header

header(*, include=None, exclude=None)

Builds header widget from its components.

Parameters:

Name Type Description Default
include Sequence[str]

names of components to render in header widget.

None
exclude Sequence[str]

names of components not to render in header widget.

None
Notes

Parameters include and exclude are mutually exclusive.

Returns:

Name Type Description
header DOMWidget

Widget that can be displayed containing all the components of this Tier's header.

Source code in cassini/ext/ipygui/components.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
def header(
    self,
    *,
    include: Union[List[str], None] = None,
    exclude: Union[List[str], None] = None,
) -> DOMWidget:
    """
    Builds header widget from its components.

    Parameters
    ----------
    include : Sequence[str]
        names of components to render in header widget.
    exclude : Sequence[str]
        names of components not to render in header widget.

    Notes
    -----
    Parameters include and exclude are mutually exclusive.

    Returns
    -------
    header : DOMWidget
        Widget that can be displayed containing all the components of this `Tier`'s header.
    """
    if include and exclude:
        raise ValueError("Only one of include or exclude can be provided")

    component_makers = self._get_header_components()

    if include:
        component_makers = {
            k: v for k, v in component_makers.items() if k in include
        }

    if exclude:
        component_makers = {
            k: v for k, v in component_makers.items() if k not in exclude
        }

    components = [self._build_header_title()]

    for name, component_builder in component_makers.items():
        component = component_builder()
        if component:
            components.append(widgetify_html(f"<h3>{name}</h3>"))
            components.append(component)

    return VBox(components)

display_highlights

display_highlights()

Display an ipywidgets.Accordian with this Tier's highlights.

Source code in cassini/ext/ipygui/components.py
461
462
463
464
465
def display_highlights(self) -> None:
    """
    Display an `ipywidgets.Accordian` with this `Tier`'s highlights.
    """
    display(self._build_highlights_accordion())

children_df

children_df(*, include=None, exclude=None)

Calls tier.children_df but returns an UnescapedDataFrame instead.

Source code in cassini/ext/ipygui/components.py
467
468
469
470
471
472
473
474
475
476
477
def children_df(
    self,
    *,
    include: Union[List[str], None] = None,
    exclude: Union[List[str], None] = None,
) -> Union[UnescapedDataFrame, None]:
    """
    Calls `tier.children_df` but returns an `UnescapedDataFrame` instead.
    """
    children_df = create_children_df(self.tier, include=include, exclude=exclude)
    return UnescapedDataFrame(children_df)

new_child

new_child()

Widget for creating new child for this Tier.

Source code in cassini/ext/ipygui/components.py
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
def new_child(self) -> DOMWidget:
    """
    Widget for creating new child for this `Tier`.
    """
    child_cls = self.tier.child_cls

    if not child_cls:
        return

    if not issubclass(child_cls, NotebookTierBase):

        def create(form: InputSequence, name: str) -> None:
            with form.status:
                obj = child_cls(
                    *self.tier.identifiers, name, project=self.tier.project
                )
                obj.setup_files()
                display(widgetify_html(obj._repr_html_()))

        form = InputSequence(
            create, Text(description="Identifier", placeholder="{child.id_regex}")
        )

        return form.as_widget()
    else:
        options = child_cls.get_templates(self.tier.project)
        mapping = {path.name: path for path in options}
        selection = Select(options=mapping.keys(), description="Template")

        def create_notebook_child(
            form: InputSequence, name: str, template: str, description: str
        ) -> None:
            with form.status:
                obj: NotebookTierBase = child_cls(
                    *self.tier.identifiers, name, project=self.tier.project
                )
                obj.setup_files(mapping[template])
                obj.description = description
                display(widgetify_html(obj._repr_html_()))

        form = InputSequence(
            create_notebook_child,
            Text(description="Identifier", placeholder="{child.id_regex}"),
            selection,
            Textarea(description="Motivation"),
        )

        return form.as_widget()

widgetify

widgetify(obj)

Allows any object to be treated like an ipywidget so it can be used in ipywidget layouts - handy!

Source code in cassini/ext/ipygui/components.py
110
111
112
113
114
115
116
117
def widgetify(obj: Any) -> Output:
    """
    Allows any object to be treated like an `ipywidget` so it can be used in `ipywidget` layouts - handy!
    """
    output = Output(height="auto", width="auto")
    with output:
        display(obj)
    return output

widgetify_html

widgetify_html(html)

Required for html which has links rather than ipywidgets HTML widget.

Parameters:

Name Type Description Default
html str

HTML to render

required

Returns:

Type Description
Output widget containing the HTML
Source code in cassini/ext/ipygui/components.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def widgetify_html(html: str) -> Output:
    """
    Required for html which has links rather than ipywidgets HTML widget.

    Parameters
    ----------
    html: str
        HTML to render

    Returns
    -------
    Output widget containing the HTML
    """
    return widgetify(WHTML(html))