9
9
from pkgutil import get_data
10
10
from typing import Any , ClassVar
11
11
12
- from cmem_plugin_base .dataintegration .plugins import TransformPlugin , WorkflowPlugin
12
+ from cmem_plugin_base .dataintegration .context import PluginContext
13
+ from cmem_plugin_base .dataintegration .plugins import PluginBase , TransformPlugin , WorkflowPlugin
13
14
from cmem_plugin_base .dataintegration .types import (
14
15
ParameterType ,
15
16
ParameterTypes ,
@@ -96,6 +97,84 @@ def __init__( # noqa: PLR0913
96
97
self .visible = visible
97
98
98
99
100
+ class PluginAction :
101
+ """Custom plugin action.
102
+
103
+ Plugin actions provide additional functionality besides the default execution.
104
+ They can be triggered from the plugin UI.
105
+ Each action is based on a method on the plugin class. Besides the self parameter,
106
+ the method can have one additional parameter of type PluginContext.
107
+ The return value of the method will be converted to a string and displayed in the UI.
108
+ The string may use Markdown formatting.
109
+ The method may return None, in which case no output will be displayed.
110
+ It may raise an exception to signal an error to the user.
111
+
112
+ :param name: The name of the method.
113
+ :param label: A human-readable label of the action
114
+ :param description: A human-readable description of the action
115
+ :param icon: An optional custom icon.
116
+ """
117
+
118
+ def __init__ (self , name : str , label : str , description : str , icon : Icon | None = None ):
119
+ self .name = name
120
+ self .label = label
121
+ self .description = description
122
+ self .icon = icon
123
+ self .validated = False
124
+ self .provide_plugin_context = False # Will be set by validate()
125
+
126
+ def validate (self , plugin_class : type ) -> None :
127
+ """Validate the action and set the `provide_plugin_context` boolean.
128
+
129
+ :param plugin_class: The plugin class
130
+ """
131
+ # Get the method from the class.
132
+ try :
133
+ method = getattr (plugin_class , self .name )
134
+ except AttributeError :
135
+ raise TypeError (
136
+ f"Plugin class '{ plugin_class .__name__ } ' does not have a method named '{ self .name } '"
137
+ ) from None
138
+ if not callable (method ):
139
+ raise TypeError (f"'{ self .name } ' in class '{ plugin_class .__name__ } ' is not a function." )
140
+
141
+ # Check parameters
142
+ parameters = list (inspect .signature (method ).parameters .values ())
143
+ if len (parameters ) == 1 :
144
+ self .provide_plugin_context = False
145
+ elif len (parameters ) - 1 == 1 :
146
+ if parameters [1 ].annotation is PluginContext :
147
+ self .provide_plugin_context = True
148
+ else :
149
+ raise TypeError (
150
+ f"Argument of method '{ self .name } ' in { plugin_class .__name__ } must "
151
+ f"be typed PluginContext (it's { parameters [1 ].annotation } )."
152
+ )
153
+ else :
154
+ raise TypeError (
155
+ f"Method '{ self .name } ' in { plugin_class .__name__ } has more than one"
156
+ f" argument (besides 'self')."
157
+ )
158
+ self .validated = True
159
+
160
+ def execute (self , plugin : PluginBase , context : PluginContext ) -> str | None :
161
+ """Call the action.
162
+
163
+ :param plugin: The plugin instance on which the action is called.
164
+ :param context: The plugin context
165
+ :return: The result of the action as string
166
+ """
167
+ if not self .validated :
168
+ raise ValueError ("Action must be validated before it can be executed." )
169
+ if self .provide_plugin_context :
170
+ result = getattr (plugin , self .name )(context )
171
+ else :
172
+ result = getattr (plugin , self .name )()
173
+ if result is None :
174
+ return None
175
+ return str (result )
176
+
177
+
99
178
class PluginDescription :
100
179
"""A plugin description.
101
180
@@ -106,6 +185,7 @@ class PluginDescription:
106
185
:param categories: The categories to which this plugin belongs to.
107
186
:param parameters: Available plugin parameters
108
187
:param icon: An optional custom plugin icon.
188
+ :param actions: Custom plugin actions.
109
189
"""
110
190
111
191
def __init__ ( # noqa: PLR0913
@@ -118,6 +198,7 @@ def __init__( # noqa: PLR0913
118
198
categories : list [str ] | None = None ,
119
199
parameters : list [PluginParameter ] | None = None ,
120
200
icon : Icon | None = None ,
201
+ actions : list [PluginAction ] | None = None ,
121
202
) -> None :
122
203
# Set the type of the plugin. Same as the class name of the plugin
123
204
# base class, e.g., 'WorkflowPlugin'.
@@ -153,6 +234,12 @@ def __init__( # noqa: PLR0913
153
234
else :
154
235
self .parameters = parameters
155
236
self .icon = icon
237
+ if actions is None :
238
+ self .actions = []
239
+ else :
240
+ self .actions = actions
241
+ for action in self .actions :
242
+ action .validate (plugin_class )
156
243
157
244
158
245
@dataclass
@@ -233,6 +320,7 @@ class Plugin:
233
320
:param categories: The categories to which this plugin belongs to.
234
321
:param parameters: Available plugin parameters.
235
322
:param icon: Optional custom plugin icon.
323
+ :param actions: Custom plugin actions
236
324
"""
237
325
238
326
plugins : ClassVar [list [PluginDescription ]] = []
@@ -246,12 +334,14 @@ def __init__( # noqa: PLR0913
246
334
categories : list [str ] | None = None ,
247
335
parameters : list [PluginParameter ] | None = None ,
248
336
icon : Icon | None = None ,
337
+ actions : list [PluginAction ] | None = None ,
249
338
):
250
339
self .label = label
251
340
self .description = description
252
341
self .documentation = documentation
253
342
self .plugin_id = plugin_id
254
343
self .icon = icon
344
+ self .actions = actions
255
345
if categories is None :
256
346
self .categories = []
257
347
else :
@@ -272,6 +362,7 @@ def __call__(self, func: type):
272
362
categories = self .categories ,
273
363
parameters = self .retrieve_parameters (func ),
274
364
icon = self .icon ,
365
+ actions = self .actions ,
275
366
)
276
367
Plugin .plugins .append (plugin_desc )
277
368
return func
@@ -304,7 +395,7 @@ def retrieve_parameters(self, plugin_class: type) -> list[PluginParameter]:
304
395
# Special handling of PluginContext parameter
305
396
if isinstance (param .param_type , PluginContextParameterType ):
306
397
param .visible = False # Should never be visible in the UI
307
- param .default_value = "" # dummy value
398
+ param .default_value = "" # default value
308
399
309
400
if param .default_value is None and sig_param .default != _empty :
310
401
param .default_value = sig_param .default
0 commit comments