Skip to content

Commit 843a5f4

Browse files
committed
Move AbstractAction to vizro.actions
1 parent fcc482a commit 843a5f4

31 files changed

+151
-144
lines changed

vizro-core/examples/scratch_dev/app.py

+38-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from vizro.actions import filter_interaction, export_data
88
from vizro.models.types import capture
99

10+
# TODO NOW: move this
11+
from vizro.actions import AbstractAction
12+
1013
df_gapminder = px.data.gapminder().query("year == 2007")
1114

1215
page_1 = vm.Page(
@@ -74,6 +77,35 @@ def my_custom_action(points_data, controls):
7477
return card_1_text, card_2_text
7578

7679

80+
# from typing import Literal
81+
#
82+
#
83+
# class f(AbstractAction):
84+
# type: Literal["f"] = "f"
85+
# points_data: str
86+
# swap: bool = False
87+
#
88+
# def function(self, points_data, controls):
89+
# """Custom action."""
90+
# clicked_point = points_data["points"][0]
91+
# x, y = clicked_point["x"], clicked_point["y"]
92+
# species = clicked_point["customdata"][0]
93+
# card_1_text = f"Clicked point has sepal length {x}, petal width {y}."
94+
# card_2_text = f"Controls are `{controls}`"
95+
# return card_1_text, card_2_text
96+
#
97+
# @property
98+
# def outputs(self):
99+
# return (
100+
# ["my_card_2.children", "my_card_1.children"] if self.swap else ["my_card_1.children", "my_card_2.children"]
101+
# )
102+
#
103+
#
104+
# from vizro.models._action._actions_chain import ActionsChain
105+
#
106+
# # vm.Graph.add_type("actions", f)
107+
# # ActionsChain.add_type("actions", f)
108+
77109
# @capture("action")
78110
# def my_custom_action():
79111
# """Custom action."""
@@ -102,13 +134,14 @@ def my_custom_action(points_data, controls):
102134
vm.Action(
103135
function=my_custom_action("scatter_chart.clickData"),
104136
# TODO NOW CHECK: make sure user-specified argument continues to take precedence
105-
# function=my_custom_action("scatter_chart.clickData", controls="my_card_1.children"),
106-
# TODO NOW CHECK: test to make sure this old way continues to work
107-
# function=my_custom_action(),
108-
# function=my_custom_action(t=4),
109-
# inputs=["scatter_chart.clickData"],
137+
# # function=my_custom_action("scatter_chart.clickData", controls="my_card_1.children"),
138+
# # TODO NOW CHECK: test to make sure this old way continues to work
139+
# # function=my_custom_action(),
140+
# # function=my_custom_action(t=4),
141+
# # inputs=["scatter_chart.clickData"],
110142
outputs=["my_card_1.children", "my_card_2.children"],
111143
),
144+
# f(swap=True, points_data="scatter_chart.clickData")
112145
],
113146
),
114147
vm.Card(id="my_card_1", text="Click on a point on the above graph."),

vizro-core/examples/scratch_dev/notes.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
TODO future: rename _on_page_load if desired and make _filter etc. public.
2525
Add deprecation warnings etc. for when legacy=True
26+
ActionsChain.add_type("actions", f) is needed as well as vm.Graph.add_type("actions", f). Remove ActionsChain and then this will be possible. Should you need to add_type at all though compared to just having AbstractAction as an allowed type? Probably yes.
2627

2728
# Put in PR/docs
2829

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from vizro.actions._abstract_action import AbstractAction
12
from vizro.actions._filter_action import _filter
2-
from vizro.actions._on_page_load_action import _on_page_load
3+
from vizro.actions._on_page_load import _on_page_load
34
from vizro.actions._parameter_action import _parameter
4-
from vizro.actions.export_data_action import export_data
5-
from vizro.actions.filter_interaction_action import filter_interaction
5+
from vizro.actions._export_data import export_data
6+
from vizro.actions._filter_interaction import filter_interaction
67

7-
__all__ = ["export_data", "filter_interaction"]
8+
__all__ = ["AbstractAction", "export_data", "filter_interaction"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from __future__ import annotations
2+
3+
import abc
4+
import inspect
5+
6+
from vizro.models._action._action import IdProperty, _BaseAction
7+
8+
9+
class AbstractAction(_BaseAction, abc.ABC):
10+
"""AbstractAction to be inserted into `actions` of relevant component.
11+
12+
To use this class, you must subclass it and define `function` and `outputs` to make a concrete action class. All
13+
built in actions follow this pattern, and it's also an option for user-defined acftions. This class is not
14+
relevant for user-defined actions using @capture("action").
15+
16+
When subclassing, you can optionally define model fields. The handling of fields depends on whether it is also
17+
present in the function signature:
18+
- static arguments, e.g. file_format = "csv": model fields, not explicitly in function signature, go through self.
19+
Uses self and not Dash State
20+
- runtime arguments, e.g. arg_name="dropdown.value": model fields, explicitly in function signature. Uses Dash
21+
State
22+
- built in runtime arguments, e.g. controls: not model fields, explicitly in function signature. Uses Dash State
23+
"""
24+
25+
_legacy = False
26+
27+
# TODO NOW COMMENT: Check schema and make sure these don't appear, comment on importance of this.
28+
29+
# TODO NOW: make keyword args only? What are actual limitations here? Don't worry much about it.
30+
@abc.abstractmethod
31+
def function(self, *args, **kwargs):
32+
"""Function that must be defined by concrete action."""
33+
pass
34+
35+
@property
36+
@abc.abstractmethod
37+
def outputs(self) -> dict[str, IdProperty]:
38+
"""Must be defined by concrete action, even if there's no output."""
39+
# TODO NOW OR IN FUTURE: handle list[str], align with Action. Maybe allow more deeply nested things too.
40+
# TODO NOW: should it handle dictionary ids too? Currently this needs overriding _get_outputs. Pattern matching
41+
# probably not needed for outputs and only for built-in inputs. Even if add more functionality here in future
42+
# we shoulod still at least the support same as Action.output so it's easy for someone to move from a function
43+
# action to a class one. In future we'd even like to just allow specifying the component id without the property.
44+
45+
# Maybe there will be some special built-in behaviour here e.g. to generate outputs automatically from
46+
# certain reserved arguments like self.targets. Would need to make sure it's not breaking if someone already
47+
# uses that variable name though.
48+
pass
49+
50+
@property
51+
def _parameters(self) -> set[str]:
52+
# Note order of parameters doesn't matter since we always handle things with keyword arguments.
53+
return set(inspect.signature(self.function).parameters)
54+
55+
@property
56+
def _runtime_args(self) -> dict[str, IdProperty]:
57+
# Since function is not a CapturedCallable, input arguments have not yet been bound. They correspond to the
58+
# model fields that are present in the function signature. This is just the user-specified runtime arguments, as
59+
# static arguments are not in the function signature (they're in self) and built in runtime arguments are not
60+
# model fields. These will be of the form {"argument_name": "dropdown.value"}.
61+
return {arg_name: getattr(self, arg_name) for arg_name in self.model_fields if arg_name in self._parameters}
62+
63+
@property
64+
def _action_name(self):
65+
return self.__class__.__name__

vizro-core/src/vizro/actions/_action_loop/_action_loop.py

-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ def _build_actions_models():
4141
List of required components for each `Action` in the `Dashboard` e.g. list[dcc.Download]
4242
4343
"""
44-
4544
return html.Div(
4645
[action.build() for action in cast(Iterable[_BaseAction], model_manager._get_models(_BaseAction))],
4746
id="app_action_models_components_div",

vizro-core/src/vizro/actions/_action_loop/_build_action_loop_callbacks.py

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from vizro.managers import model_manager
88
from vizro.managers._model_manager import ModelID
9-
from vizro.models import Action
109
from vizro.models._action._action import _BaseAction
1110
from vizro.models._action._actions_chain import ActionsChain
1211

vizro-core/src/vizro/actions/_action_loop/_get_action_loop_components.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
from dash import dcc, html
44

55
from vizro.managers import model_manager
6-
from vizro.models import Action
7-
from vizro.models._action._actions_chain import ActionsChain
86
from vizro.models._action._action import _BaseAction
7+
from vizro.models._action._actions_chain import ActionsChain
98

109

1110
def _get_action_loop_components() -> html.Div:

vizro-core/src/vizro/actions/_actions_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
)
2222

2323
if TYPE_CHECKING:
24-
from vizro.models import Action, VizroBaseModel
24+
from vizro.models import VizroBaseModel
2525
from vizro.models.type import ActionsType
2626

2727
ValidatedNoneValueType = Union[SingleValueType, MultiValueType, None, list[None], list[SingleValueType]]

vizro-core/src/vizro/actions/export_data_action.py vizro-core/src/vizro/actions/_export_data.py

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
import importlib
2-
from typing import Any
2+
from collections.abc import Iterable
3+
from typing import Any, Literal, cast
34

4-
from dash import ctx, dcc
5+
from dash import Output, ctx, dcc
56
from typing_extensions import Literal
67

8+
from vizro.actions import AbstractAction
79
from vizro.actions._actions_utils import _apply_filters, _get_unfiltered_data
810
from vizro.managers import model_manager
9-
from vizro.managers._model_manager import ModelID
10-
11-
from collections.abc import Iterable
12-
from typing import Literal, cast
13-
14-
from dash import Output
15-
16-
from vizro.managers._model_manager import FIGURE_MODELS
11+
from vizro.managers._model_manager import FIGURE_MODELS, ModelID
1712
from vizro.models import VizroBaseModel
18-
from vizro.models._action._action import AbstractAction, Controls
19-
20-
from typing import Literal
13+
from vizro.models._action._action import Controls
2114

2215

2316
# TODO NOW: check how schema for this is generated.

vizro-core/src/vizro/actions/_filter_action.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
from typing import Any, Callable
1+
from typing import Any, Callable, Literal
22

33
import pandas as pd
44
from dash import ctx
55
from pydantic import Field
66

7+
from vizro.actions import AbstractAction
78
from vizro.actions._actions_utils import _get_modified_page_figures
89
from vizro.managers._model_manager import ModelID, model_manager
9-
from vizro.models._action._action import AbstractAction, Controls
10-
11-
from typing import Literal
10+
from vizro.models._action._action import Controls
1211

1312

1413
class _filter(AbstractAction):

vizro-core/src/vizro/actions/filter_interaction_action.py vizro-core/src/vizro/actions/_filter_interaction.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
from typing import Any
1+
from typing import Any, Literal
22

33
from dash import ctx
44
from pydantic import Field
55

6+
from vizro.actions import AbstractAction
67
from vizro.actions._actions_utils import _get_modified_page_figures
78
from vizro.managers._model_manager import ModelID, model_manager
8-
from vizro.models._action._action import AbstractAction, Controls
9-
10-
from typing import Literal
9+
from vizro.models._action._action import Controls
1110

1211

1312
class filter_interaction(AbstractAction):

vizro-core/src/vizro/actions/_on_page_load_action.py vizro-core/src/vizro/actions/_on_page_load.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
from typing import Any
1+
from typing import Any, Literal
22

33
from dash import ctx
44
from pydantic import Field
55

6+
from vizro.actions import AbstractAction
67
from vizro.actions._actions_utils import _get_modified_page_figures
78
from vizro.managers._model_manager import ModelID, model_manager
8-
from vizro.models._action._action import AbstractAction, Controls
9-
10-
from typing import Literal
9+
from vizro.models._action._action import Controls
1110

1211

1312
class _on_page_load(AbstractAction):

vizro-core/src/vizro/actions/_parameter_action.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
from typing import Any
1+
from typing import Any, Literal
22

33
from dash import ctx
44
from pydantic import Field
55

6+
from vizro.actions import AbstractAction
67
from vizro.actions._actions_utils import _get_modified_page_figures
78
from vizro.managers._model_manager import ModelID, model_manager
8-
from vizro.models._action._action import AbstractAction, Controls
9-
10-
from typing import Literal
9+
from vizro.models._action._action import Controls
1110

1211

1312
class _parameter(AbstractAction):

vizro-core/src/vizro/models/_action/_action.py

+2-65
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from __future__ import annotations
22

33
import inspect
4-
import abc
54
import logging
65
import re
76
from collections.abc import Collection, Iterable, Mapping
87
from pprint import pformat
9-
from typing import Annotated, Any, NewType, TypedDict, Union, cast, Callable
8+
from typing import Annotated, Any, Literal, NewType, TypedDict, Union, cast
109

1110
from dash import Input, Output, State, callback, html
1211
from dash.development.base_component import Component
@@ -16,9 +15,7 @@
1615
from vizro.managers._model_manager import ModelID, model_manager
1716
from vizro.models import VizroBaseModel
1817
from vizro.models._models_utils import _log_call
19-
from vizro.models.types import CapturedCallable, ControlType, capture, validate_captured_callable
20-
21-
from typing import Literal
18+
from vizro.models.types import CapturedCallable, ControlType, validate_captured_callable
2219

2320
logger = logging.getLogger(__name__)
2421

@@ -37,7 +34,6 @@ class Controls(TypedDict):
3734
filter_interaction: list[dict[str, Any]]
3835

3936

40-
# TODO NOW: probably split into one model per file structure
4137
class _BaseAction(VizroBaseModel):
4238
def _get_control_states(self, control_type: ControlType) -> list[State]:
4339
"""Gets list of `States` for selected `control_type` that appear on page where this Action is defined."""
@@ -322,62 +318,3 @@ def _runtime_args(self) -> dict[str, IdProperty]:
322318
@property
323319
def _action_name(self):
324320
return self.function._function.__name__
325-
326-
327-
class AbstractAction(_BaseAction, abc.ABC):
328-
"""AbstractAction to be inserted into `actions` of relevant component.
329-
330-
To use this class, you must subclass it and define `function` and `outputs` to make a concrete action class. All
331-
built in actions follow this pattern, and it's also an option for user-defined acftions. This class is not
332-
relevant for user-defined actions using @capture("action").
333-
334-
When subclassing, you can optionally define model fields. The handling of fields depends on whether it is also
335-
present in the function signature:
336-
- static arguments, e.g. file_format = "csv": model fields, not explicitly in function signature, go through self.
337-
Uses self and not Dash State
338-
- runtime arguments, e.g. arg_name="dropdown.value": model fields, explicitly in function signature. Uses Dash
339-
State
340-
- built in runtime arguments, e.g. controls: not model fields, explicitly in function signature. Uses Dash State
341-
"""
342-
343-
_legacy = False
344-
345-
# TODO NOW COMMENT: Check schema and make sure these don't appear, comment on importance of this.
346-
347-
# TODO NOW: make keyword args only? What are actual limitations here? Don't worry much about it.
348-
@abc.abstractmethod
349-
def function(self, *args, **kwargs):
350-
"""Function that must be defined by concrete action."""
351-
pass
352-
353-
@property
354-
@abc.abstractmethod
355-
def outputs(self) -> dict[str, IdProperty]:
356-
"""Must be defined by concrete action, even if there's no output."""
357-
# TODO NOW OR IN FUTURE: handle list[str], align with Action. Maybe allow more deeply nested things too.
358-
# TODO NOW: should it handle dictionary ids too? Currently this needs overriding _get_outputs. Pattern matching
359-
# probably not needed for outputs and only for built-in inputs. Even if add more functionality here in future
360-
# we shoulod still at least the support same as Action.output so it's easy for someone to move from a function
361-
# action to a class one. In future we'd even like to just allow specifying the component id without the property.
362-
363-
# Maybe there will be some special built-in behaviour here e.g. to generate outputs automatically from
364-
# certain reserved arguments like self.targets. Would need to make sure it's not breaking if someone already
365-
# uses that variable name though.
366-
pass
367-
368-
@property
369-
def _parameters(self) -> set[str]:
370-
# Note order of parameters doesn't matter since we always handle things with keyword arguments.
371-
return set(inspect.signature(self.function).parameters)
372-
373-
@property
374-
def _runtime_args(self) -> dict[str, IdProperty]:
375-
# Since function is not a CapturedCallable, input arguments have not yet been bound. They correspond to the
376-
# model fields that are present in the function signature. This is just the user-specified runtime arguments, as
377-
# static arguments are not in the function signature (they're in self) and built in runtime arguments are not
378-
# model fields. These will be of the form {"argument_name": "dropdown.value"}.
379-
return {arg_name: getattr(self, arg_name) for arg_name in self.model_fields if arg_name in self._parameters}
380-
381-
@property
382-
def _action_name(self):
383-
return self.__class__.__name__

vizro-core/src/vizro/models/_action/_actions_chain.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pydantic import ValidationInfo
55

66
from vizro.managers import model_manager
7-
from vizro.models import Action, VizroBaseModel
7+
from vizro.models import VizroBaseModel
88
from vizro.models.types import ActionsType
99

1010

0 commit comments

Comments
 (0)