Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Truncate InvalidCallbackReturnValue size #2663

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion dash/_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import re
from textwrap import dedent
from keyword import iskeyword
from typing import Any
from typing_extensions import Final

import flask

from ._grouping import grouping_len, map_grouping
Expand All @@ -15,6 +18,8 @@
clean_property_name,
)

TRUNCATE_AT: Final[int] = 100


def validate_callback(outputs, inputs, state, extra_args, types):
Input, Output, State = types
Expand Down Expand Up @@ -209,11 +214,23 @@ def validate_multi_return(output_lists, output_values, callback_id):
)


def truncate_object(obj: Any, at: int) -> str:
"""Return a truncated string representation of an object."""
obj_stringified = str(obj)
size = len(obj_stringified)

if size <= at:
return obj_stringified

return obj_stringified[:at] + f"... (truncated - {size} characters total)"


def fail_callback_output(output_value, output):
valid_children = (str, int, float, type(None), Component)
valid_props = (str, int, float, type(None), tuple, MutableSequence)

def _raise_invalid(bad_val, outer_val, path, index=None, toplevel=False):
bad_val_stringified = truncate_object(bad_val, TRUNCATE_AT)
bad_type = type(bad_val).__name__
outer_id = f"(id={outer_val.id:s})" if getattr(outer_val, "id", False) else ""
outer_type = type(outer_val).__name__
Expand Down Expand Up @@ -244,7 +261,7 @@ def _raise_invalid(bad_val, outer_val, path, index=None, toplevel=False):
{location}
and has string representation
`{bad_val}`
`{bad_val_stringified}`
In general, Dash properties can only be
dash components, strings, dictionaries, numbers, None,
Expand Down
85 changes: 84 additions & 1 deletion tests/integration/devtools/test_devtools_error_handling.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: UTF-8 -*-
from dash import Dash, Input, Output, html, dcc
from dash import _validate, Dash, Input, Output, html, dcc
from dash.exceptions import PreventUpdate


Expand Down Expand Up @@ -260,3 +260,86 @@ def update_outputs(n_clicks):
dash_duo.wait_for_text_to_equal(dash_duo.devtools_error_count_locator, "1")
dash_duo.find_element(".test-devtools-error-toggle").click()
dash_duo.percy_snapshot("devtools - multi output Python exception - open")


def test_dveh006_truncate_callback(dash_duo):
app = Dash(__name__)

from dataclasses import dataclass
from typing import List, Dict, Any

@dataclass
class GenericItemA:
attribute_1: int
attribute_2: str

@dataclass
class GenericItemB:
key: str
value: List[str]

@dataclass
class GenericItemC:
identifier: int
description: str

@dataclass
class GenericOptions:
option_key: str
option_value: Any

@dataclass
class GenericDataModel:
ItemListA: List[GenericItemA]
SingleItemB: GenericItemB
NestedItemListC: List[List[GenericItemC]]
Options: GenericOptions
Metadata: Dict
AdditionalInfo: Any

app.layout = html.P(id="output")

@app.callback(Output("output", "children"), Input("url", "href"))
def get_width(_):
item_list_a = [
GenericItemA(attribute_1=123, attribute_2="Alpha"),
GenericItemA(attribute_1=456, attribute_2="Beta")
]

single_item_b = GenericItemB(key="Key1", value=["Item1", "Item2"])

nested_item_list_c = [
[GenericItemC(identifier=101, description="Description1")],
[GenericItemC(identifier=102, description="Description2")]
]

generic_options = GenericOptions(option_key="Option1", option_value="Value1")

generic_metadata = {"meta_key": "meta_value"}

additional_info = "Generic information"

# Creating an instance of GenericDataModel
generic_data_model = GenericDataModel(
ItemListA=item_list_a,
SingleItemB=single_item_b,
NestedItemListC=nested_item_list_c,
Options=generic_options,
Metadata=generic_metadata,
AdditionalInfo=additional_info
)

return generic_data_model

dash_duo.start_server(
app,
debug=True,
use_reloader=False,
use_debugger=True,
dev_tools_hot_reload=False,
)

dash_duo.find_element(".dash-debug-menu").click()
dash_duo.wait_for_text_to_equal(dash_duo.devtools_error_count_locator, "1")

assert len(get_error_html(dash_duo, 0)) == _validate.TRUNCATE_AT