diff --git a/panel/pane/base.py b/panel/pane/base.py index 42663c8c06..aeb18115b9 100644 --- a/panel/pane/base.py +++ b/panel/pane/base.py @@ -44,6 +44,8 @@ def panel(obj, **kwargs): """ if isinstance(obj, Viewable): return obj + elif hasattr(obj, '__panel__'): + return panel(obj.__panel__()) if kwargs.get('name', False) is None: kwargs.pop('name') pane = PaneBase.get_pane_type(obj, **kwargs)(obj, **kwargs) diff --git a/panel/pipeline.py b/panel/pipeline.py index 348aef863c..2bde821bfa 100644 --- a/panel/pipeline.py +++ b/panel/pipeline.py @@ -11,6 +11,7 @@ from .widgets import Button, Select from .param import Param from .util import param_reprs +from .viewable import Viewer class PipelineError(RuntimeError): @@ -102,7 +103,7 @@ def get_breadths(node, graph, depth=0, breadths=None): -class Pipeline(param.Parameterized): +class Pipeline(Viewer): """ A Pipeline represents a directed graph of stages, which each return a panel object to render. A pipeline therefore represents @@ -222,6 +223,9 @@ def __init__(self, stages=[], graph={}, **params): self.add_stage(name, stage, **kwargs) self.define_graph(graph) + def __panel__(self): + return self.layout + def _validate(self, stage): if any(stage is s for n, (s, kw) in self._stages.items()): raise ValueError('Stage %s is already in pipeline' % stage) @@ -527,9 +531,6 @@ def tap_renderer(plot, element): ) return plot - def _repr_mimebundle_(self, include=None, exclude=None): - return self.layout._repr_mimebundle_(include, exclude) - #---------------------------------------------------------------- # Public API #---------------------------------------------------------------- diff --git a/panel/viewable.py b/panel/viewable.py index ff98bd5c9c..2705ebe747 100644 --- a/panel/viewable.py +++ b/panel/viewable.py @@ -769,3 +769,35 @@ def server_doc(self, doc=None, title=None, location=True): add_to_doc(model, doc) if location: self._add_location(doc, location, model) return doc + + +class Viewer(param.Parameterized): + """ + A baseclass for custom components that behave like a Panel object. + By implementing __panel__ method an instance of this class will + behave like the returned Panel component when placed in a layout, + render itself in a notebook and provide show and servable methods. + """ + + def __panel__(self): + """ + Subclasses should return a Panel component to be rendered. + """ + raise NotImplementedError + + def servable(self, title=None, location=True): + return self.__panel__().servable(title, location) + + servable.__doc__ = ServableMixin.servable.__doc__ + + def show(self, title=None, port=0, address=None, websocket_origin=None, + threaded=False, verbose=True, open=True, location=True, **kwargs): + return self.__panel__().show( + title, port, address, websocket_origin, threaded, + verbose, open, location, **kwargs + ) + + show.__doc__ = ServableMixin.show.__doc__ + + def _repr_mimebundle_(self, include=None, exclude=None): + return self.__panel__._repr_mimebundle_(include, exclude)