8000 Include a debug/info decorator · Issue #2062 · holoviz/panel · GitHub
[go: up one dir, main page]

Skip to content

Include a debug/info decorator #2062

@hyamanieu

Description

@hyamanieu

Current status

In its current status, interactive Panel objects raising errors will either create the traceback in the jupyter cell (depending on panel.config.console_output) or inside the console in a server context.

This is inconvenient in a production context as a raised error may just look like an unresponsive application.

Short description

In a production context as well as during analytical work, quickly knowing why an error is raised is very helpful.

I would like to suggest a decorator to add to any function that is used to render a dynamic pane, e.g. using pn.depends, param.depends, or pn.interact.

This solution could be either included in the standard library, or if it is too niche inside a template or in the gallery.

Proposed solution

add the following decorator in the standard library

import sys
import traceback
import functools
import panel as pn

info_layout = pn.Tabs(closable=True)

def status_decorator(f: callable =None, behavior: str='raise', info_layout: pn.layout.base.ListPanel=info_layout) -> callable: 
    """   
    Parameters
    ----------
    f : callable, optional
        The function to decorate.
    behavior : str, optional
        If 'replace', the object which should have been rendered will be replaced by the error message and the error will not be raised. 
        If 'raise', the error will be raised, meaning the target object will look frozen. 
        If 'erase', the function will return None and nothing will be rendered in place of the target object. 
        The default is 'raise'.
        
        In all cases, the error message will be appended to the list-like panel object 'info_layout'
    info_layout : pn.layout.base.ListPanel, optional
        A list-like panel object where each element is an error status. The default is info_layout.


    Returns
    -------
    callable
        The decorated function.

    """
    
    def mydec(f):
        @functools.wraps(f)
        def wrapper(*args,**kwargs):
            try:
                #info_layout.clear()
                return f(*args,**kwargs)
            except Exception as e:
                v, e, tb = sys.exc_info()
                stack = traceback.extract_tb(tb)
                (filename, line, procname, text) = stack[-1]
                info = pn.pane.Alert(object=repr(stack[-1]), height=100)
                info_layout.append((procname,info))
                if behavior == 'replace':
                    return info
                elif behavior == 'raise':
                    raise e
                elif behavior == 'erase':
                    return
                    
        return wrapper
            
    if f is None:
        return mydec
    else:
        return mydec(f)

The ListPanel object I am using is Tabs, so that the user can close it once they acknowledge they read it. Whenever #1555 is closed, any type of list type layout would work well.

Could be also associated with a logging, see #1978 .

Additionally, it could become a class decorator so that several functions can be overwritten. For example a user could redefine what happens within the "try" close, like activating a loading spinner or so.

in context

pn.interact

Here order is important! The status_decorator must come after the interact decorator.

pn.extension()
@pn.interact(x=True, y=1.0)
@status_decorator(replace=True)
def g(x, y):
    if y>2:
        raise ValueError('y too big!')
    return (x, y)
g
interact_dec.mp4

pn.depends with a template

import holoviews as hv
pn.extension('echarts')

bootstrap = pn.template.BootstrapTemplate(title='Bootstrap Template')

pn.config.sizing_mode = 'stretch_width'

xs = np.linspace(0, np.pi)
freq = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
phase = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)




          
@status_decorator
@pn.depends(freq=freq, phase=phase)
def sine(freq, phase):
    return hv.Curve((xs, np.sin(xs*freq+phase))).opts(
        responsive=True, min_height=400)


@pn.depends(freq=freq)
@status_decorator(behavior='replace')   
def throwerr(freq):
    if freq>8:
        raise ValueError("I don't like your freq")
    return pn.indicators.Gauge(name='Frequency', value=freq, bounds=(0, 8))

 
@pn.depends(freq=freq)
@status_decorator(behavior='raise')  
def throwerr2(freq):
    if freq>8:
        raise ValueError("I don't like your freq")
    return pn.indicators.Number(name='Frequency', value=freq)

bootstrap.sidebar.append(freq)
bootstrap.sidebar.append(phase)

bootstrap.sidebar.append(info_layout)

bootstrap.main.append(
    pn.Row(
        pn.Card(hv.DynamicMap(sine), title='Sine'),
        throwerr,
        throwerr2
    )
)


bootstrap.modal.append(pn.pane.Markdown('## HELLO!'))

bootstrap.servable();
template_depends_dec.mp4

param.depends

class A(param.Parameterized):

    with_throttled_enabled = param.Range(
        default=(100, 250),
        bounds=(0, 250),
    )

    def __init__(self, **params):
        super().__init__(**params)
        widgets = {
            "with_throttled_enabled": {
                "type": pn.widgets.IntRangeSlider,
                "throttled": False,
            },
        }
        self.controls = pn.Param(self, widgets=widgets)

        
    @param.depends("controls")
    @status_decorator(behavior='erase')
    def calculation(self):
        if self.with_throttled_enabled[0] > 130:
            raise ValueError('too low')
        return pn.Pane((self.with_throttled_enabled), min_width=200)

    @param.depends("controls")
    @status_decorator(behavior='erase')
    def buggy_calculation(self):
        return pn.Pane(self.with_throttled_enabled[1]/self.with_throttled_enabled[0], min_width=200)

a = A()
pn.Column(a.controls, pn.pane.Markdown('### will bug above 130'), a.calculation, pn.pane.Markdown('### division by 0'), a.buggy_calculation, info_layout)
param_depends.mp4

Additional context

After some feedback from the main dev I can submit a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: discussionRequiring community discussiontype: enhancementMinor feature or improvement to an existing feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0