Skip to content

Commit

Permalink
New more intuitive and more Pythonic way of return value handling
Browse files Browse the repository at this point in the history
  • Loading branch information
petar-qb committed Nov 23, 2023
1 parent ed2005f commit e7a4831
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 43 deletions.
9 changes: 6 additions & 3 deletions vizro-core/src/vizro/models/_action/_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,16 @@ def _action_callback_function(self, **inputs: Dict[str, Any]) -> Dict[str, Any]:
logger.debug(f"Action inputs: {inputs}")

# Invoking the action's function
return_value = self.function(**inputs) or []
return_value = self.function(**inputs)

# Action callback outputs
outputs = list(ctx.outputs_grouping.keys())
outputs.remove("action_finished")

if hasattr(return_value, "_asdict") and hasattr(return_value, "_fields"):
if return_value is None and len(outputs) == 0:
# Action has no outputs and returns None.
return_dict = {}
elif hasattr(return_value, "_asdict") and hasattr(return_value, "_fields"):
# return_value is a namedtuple.
if set(return_value._fields) != set(outputs):
raise ValueError(
Expand All @@ -116,7 +119,7 @@ def _action_callback_function(self, **inputs: Dict[str, Any]) -> Dict[str, Any]:
)
return_dict = return_value._asdict()
else:
if isinstance(return_value, (str, dict)) or not isinstance(return_value, Collection):
if len(outputs) == 1 or not isinstance(return_value, Collection) or len(return_value) == 0:
return_value = [return_value]

if len(return_value) != len(outputs):
Expand Down
93 changes: 53 additions & 40 deletions vizro-core/tests/unit/vizro/models/_action/test_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,47 +225,45 @@ def test_get_callback_mapping_with_inputs_and_outputs( # pylint: disable=too-ma
@pytest.mark.parametrize(
"custom_action_function_mock_return, callback_context_set_outputs_grouping, expected_function_return_value",
[
# custom action function return value - None
(None, [], {"action_finished": None}),
# custom action function return value - single value
("new_value", ["component_1_property"], {"action_finished": None, "component_1_property": "new_value"}),
# custom action function return value - list of values
# no outputs
(None, [], {}),
# single output
(None, ["component_1_property"], {"component_1_property": None}),
(123, ["component_1_property"], {"component_1_property": 123}),
("value", ["component_1_property"], {"component_1_property": "value"}),
((), ["component_1_property"], {"component_1_property": ()}),
(("value"), ["component_1_property"], {"component_1_property": ("value")}),
(("value_1", "value_2"), ["component_1_property"], {"component_1_property": ("value_1", "value_2")}),
([], ["component_1_property"], {"component_1_property": []}),
(["value"], ["component_1_property"], {"component_1_property": ["value"]}),
(["value_1", "value_2"], ["component_1_property"], {"component_1_property": ["value_1", "value_2"]}),
({}, ["component_1_property"], {"component_1_property": {}}),
({"key_1": "value_1"}, ["component_1_property"], {"component_1_property": {"key_1": "value_1"}}),
(
["new_value", "new_value_2"],
["component_1_property", "component_2_property"],
{"action_finished": None, "component_1_property": "new_value", "component_2_property": "new_value_2"},
{"key_1": "value_1", "key_2": "value_2"},
["component_1_property"],
{"component_1_property": {"key_1": "value_1", "key_2": "value_2"}},
),
# custom action function return value - tuple
# multiple outputs
(
("new_value", "new_value_2"),
"ab",
["component_1_property", "component_2_property"],
{"action_finished": None, "component_1_property": "new_value", "component_2_property": "new_value_2"},
{"component_1_property": "a", "component_2_property": "b"},
),
# custom action function return value - dictionary
(
{"component_1_property": "new_value"},
["component_1_property"],
{"action_finished": None, "component_1_property": {"component_1_property": "new_value"}},
("value_1", "value_2"),
["component_1_property", "component_2_property"],
{"component_1_property": "value_1", "component_2_property": "value_2"},
),
(
({"component_1_property": "new_value", "component_2_property": "new_value_2"}),
["component_1_property"],
{
"action_finished": None,
"component_1_property": {
"component_1_property": "new_value",
"component_2_property": "new_value_2",
},
},
["value_1", "value_2"],
["component_1_property", "component_2_property"],
{"component_1_property": "value_1", "component_2_property": "value_2"},
),
(
({"component_1_property": "new_value"}, {"component_2_property": "new_value_2"}),
{"key_1": "value_1", "key_2": "value_2"},
["component_1_property", "component_2_property"],
{
"action_finished": None,
"component_1_property": {"component_1_property": "new_value"},
"component_2_property": {"component_2_property": "new_value_2"},
},
{"component_1_property": "key_1", "component_2_property": "key_2"},
),
],
indirect=["custom_action_function_mock_return", "callback_context_set_outputs_grouping"],
Expand All @@ -275,21 +273,23 @@ def test_action_callback_function_return_value_valid(
):
action = Action(function=custom_action_function_mock_return())
result = action._action_callback_function()
assert result == expected_function_return_value
expected = {"action_finished": None, **expected_function_return_value}
assert result == expected

@pytest.mark.parametrize(
"custom_action_function_mock_return, callback_context_set_outputs_grouping, expected_function_return_value",
[
# custom action function return value - namedtuple
# single outputs
(
(namedtuple("outputs", ["component_1_property"])("new_value")),
["component_1_property"],
{"action_finished": None, "component_1_property": "new_value"},
{"component_1_property": "new_value"},
),
# multiple outputs
(
(namedtuple("outputs", ["component_1_property", "component_2_property"])("new_value", "new_value_2")),
["component_1_property", "component_2_property"],
{"action_finished": None, "component_1_property": "new_value", "component_2_property": "new_value_2"},
{"component_1_property": "new_value", "component_2_property": "new_value_2"},
),
],
indirect=["custom_action_function_mock_return", "callback_context_set_outputs_grouping"],
Expand All @@ -299,22 +299,35 @@ def test_action_callback_function_return_value_valid_namedtuple(
):
action = Action(function=custom_action_function_mock_return())
result = action._action_callback_function()
assert result == expected_function_return_value
expected = {"action_finished": None, **expected_function_return_value}
assert result == expected

@pytest.mark.parametrize(
"custom_action_function_mock_return, callback_context_set_outputs_grouping",
[
(None, ["component_1_property"]),
("new_value", []),
("new_value", ["component_1_property", "component_2_property"]),
(None, ["component_1_property", "component_2_property"]),
(123, []),
(123, ["component_1_property", "component_2_property"]),
("", []),
("ab", []),
("ab", ["component_1_property", "component_2_property", "component_3_property"]),
((), []),
(("new_value"), []),
(("new_value"), ["component_1_property", "component_2_property"]),
(("new_value", "new_value_2"), []),
(("new_value", "new_value_2"), ["component_1_property", "component_2_property", "component_3_property"]),
([], []),
(["new_value"], []),
(["new_value"], ["component_1_property", "component_2_property"]),
(["new_value", "new_value_2"], ["component_1_property"]),
(["new_value", "new_value_2"], []),
(["new_value", "new_value_2"], ["component_1_property", "component_2_property", "component_3_property"]),
({}, []),
({"component_1_property": "new_value"}, []),
({"component_1_property": "new_value"}, ["component_1_property", "component_2_property"]),
({"component_1_property": "new_value", "component_2_property": "new_value_2"}, []),
(
{"component_1_property": "new_value", "component_2_property": "new_value_2"},
["component_1_property", "component_2_property"],
["component_1_property", "component_2_property", "component_3_property"],
),
],
indirect=True,
Expand Down

0 comments on commit e7a4831

Please sign in to comment.