Skip to content

Commit c399cfa

Browse files
feat(lab-3537): handle events as handlers for plugin (#1878)
Co-authored-by: Josselin BUILS <[email protected]>
1 parent 81b5e0a commit c399cfa

File tree

8 files changed

+150
-13
lines changed

8 files changed

+150
-13
lines changed

src/kili/core/graphql/operations/plugin/mutations.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
GQL_CREATE_PLUGIN_RUNNER = """
1616
mutation(
17+
$eventMatcher: [String!]
1718
$handlerTypes: [PluginHandlerType!]
1819
$pluginName: String!
1920
) {
2021
data: createPluginRunner(
2122
data: {
23+
eventMatcher: $eventMatcher
2224
handlerTypes: $handlerTypes
2325
pluginName: $pluginName
2426
}
@@ -28,13 +30,15 @@
2830

2931
GQL_CREATE_WEBHOOK = """
3032
mutation(
33+
$eventMatcher: [String!]
3134
$handlerTypes: [PluginHandlerType!]
3235
$pluginName: String!
3336
$webhookUrl: String!
3437
$header: String
3538
) {
3639
data: createWebhook(
3740
data: {
41+
eventMatcher: $eventMatcher
3842
handlerTypes: $handlerTypes
3943
header: $header
4044
pluginName: $pluginName
@@ -58,11 +62,13 @@
5862

5963
GQL_UPDATE_PLUGIN_RUNNER = """
6064
mutation(
65+
$eventMatcher: [String!]
6166
$handlerTypes: [PluginHandlerType!]
6267
$pluginName: String!
6368
) {
6469
data: updatePluginRunner(
6570
data: {
71+
eventMatcher: $eventMatcher
6672
handlerTypes: $handlerTypes
6773
pluginName: $pluginName
6874
}
@@ -72,13 +78,15 @@
7278

7379
GQL_UPDATE_WEBHOOK = """
7480
mutation(
81+
$eventMatcher: [String!]
7582
$handlerTypes: [PluginHandlerType!]
7683
$pluginName: String!
7784
$webhookUrl: String!
7885
$header: String
7986
) {
8087
data: updateWebhook(
8188
data: {
89+
eventMatcher: $eventMatcher
8290
handlerTypes: $handlerTypes
8391
header: $header
8492
pluginName: $pluginName

src/kili/entrypoints/mutations/plugins/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def upload_plugin(
2626
plugin_path: Optional[str] = None,
2727
plugin_name: Optional[str] = None,
2828
verbose: bool = True,
29+
event_matcher: Optional[List[str]] = None,
2930
**kwargs, # pylint: disable=missing-param-doc
3031
) -> LiteralString:
3132
"""Uploads a plugin.
@@ -36,6 +37,7 @@ def upload_plugin(
3637
- a folder containing a main.py (mandatory) and a requirements.txt (optional)
3738
- a .py file
3839
plugin_name: name of your plugin, if not provided, it will be the name from your file
40+
event_matcher: List of events for which the plugin should be called.
3941
verbose: If false, minimal logs are displayed
4042
4143
Returns:
@@ -60,6 +62,7 @@ def upload_plugin(
6062
plugin_name,
6163
verbose,
6264
self.http_client,
65+
event_matcher,
6366
).create_plugin()
6467

6568
@typechecked
@@ -70,6 +73,7 @@ def create_webhook(
7073
header: Optional[str] = None,
7174
verbose: bool = True,
7275
handler_types: Optional[List[str]] = None,
76+
event_matcher: Optional[List[str]] = None,
7377
) -> str:
7478
# pylint: disable=line-too-long,too-many-arguments
7579
"""Create a webhook linked to Kili's events.
@@ -92,6 +96,7 @@ def create_webhook(
9296
handler_types: List of actions for which the webhook should be called.
9397
Possible variants: `onSubmit`, `onReview`.
9498
By default, is [`onSubmit`, `onReview`].
99+
event_matcher: List of events for which the webhook should be called.
95100
96101
Returns:
97102
A string which indicates if the mutation was successful,
@@ -107,6 +112,7 @@ def create_webhook(
107112
header,
108113
verbose,
109114
handler_types,
115+
event_matcher,
110116
).create_webhook()
111117

112118
@typechecked
@@ -117,6 +123,7 @@ def update_webhook(
117123
new_header: Optional[str] = None,
118124
verbose: bool = True,
119125
handler_types: Optional[List[str]] = None,
126+
event_matcher: Optional[List[str]] = None,
120127
) -> str:
121128
# pylint: disable=line-too-long,too-many-arguments
122129
"""Update a webhook linked to Kili's events.
@@ -131,6 +138,7 @@ def update_webhook(
131138
handler_types: List of actions for which the webhook should be called.
132139
Possible variants: `onSubmit`, `onReview`.
133140
By default, is [`onSubmit`, `onReview`]
141+
event_matcher: List of events for which the webhook should be called.
134142
135143
Returns:
136144
A string which indicates if the mutation was successful,
@@ -146,6 +154,7 @@ def update_webhook(
146154
new_header,
147155
verbose,
148156
handler_types,
157+
event_matcher,
149158
).update_webhook()
150159

151160
@typechecked
@@ -203,6 +212,7 @@ def update_plugin(
203212
plugin_path: Optional[str] = None,
204213
plugin_name: Optional[str] = None,
205214
verbose: bool = True,
215+
event_matcher: Optional[List[str]] = None,
206216
**kwargs, # pylint: disable=missing-param-doc
207217
) -> LiteralString:
208218
"""Update a plugin with new code.
@@ -213,6 +223,7 @@ def update_plugin(
213223
- a folder containing a main.py (mandatory) and a requirements.txt (optional)
214224
- a .py file
215225
plugin_name: Name of the plugin
226+
event_matcher: List of events names and/or globs for which the plugin should be called.
216227
verbose: If false, minimal logs are displayed
217228
218229
Returns:
@@ -240,4 +251,5 @@ def update_plugin(
240251
plugin_name,
241252
verbose,
242253
self.http_client,
254+
event_matcher,
243255
).update_plugin()

src/kili/entrypoints/queries/plugins/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def get_plugin_status(
113113
plugin_name,
114114
verbose,
115115
self.http_client,
116+
event_matcher=None,
116117
).get_plugin_runner_status()
117118

118119
@typechecked

src/kili/services/plugins/model.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class PluginCore:
2121
on_custom_interface_click(self, label: Dict, asset_id: str)
2222
on_project_updated(self, settings_updated: List[Dict])
2323
on_send_back_to_queue(self, asset_id: str)
24+
on_event(self, payload: Dict)
2425
2526
!!! warning
2627
if using a custom init, be sure to call super().__init__()
@@ -192,3 +193,15 @@ def on_send_back_to_queue(self, asset_id: str):
192193
"""
193194
# pylint: disable=unused-argument
194195
self.logger.warning("Handler is in active development.")
196+
197+
def on_event(
198+
self,
199+
payload: Dict,
200+
) -> None:
201+
"""Handler for all events, triggered when an event is triggered.
202+
203+
Args:
204+
payload: Dict.
205+
"""
206+
# pylint: disable=unused-argument
207+
self.logger.warning("Method not implemented. Define a custom on_event on your plugin")

src/kili/services/plugins/upload.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,13 @@ def check_file_is_txt(path: Path, verbose: bool = True) -> bool:
7474
return check_file_mime_type(path, mime_extensions_for_txt_files, verbose)
7575

7676

77-
def check_file_contains_handler(path: Path) -> Tuple[bool, Optional[List[str]]]:
78-
"""Return true if the file contain PluginHandler Class."""
77+
def check_file_contains_handler(path: Path) -> Tuple[bool, Optional[List[str]], bool]:
78+
"""Test if the file is a python file with the right content.
79+
80+
Return true if the file contain PluginHandler Class.
81+
Return the list of handlers or None if they are present in the file.
82+
Return true if the file contains on_event method.
83+
"""
7984
with path.open(encoding="utf-8") as file:
8085
module = ast.parse(file.read())
8186
for node in module.body:
@@ -85,8 +90,12 @@ def check_file_contains_handler(path: Path) -> Tuple[bool, Optional[List[str]]]:
8590
for child in node.body
8691
if isinstance(child, ast.FunctionDef) and child.name in POSSIBLE_HANDLERS
8792
]
88-
return True, handlers
89-
return False, None
93+
has_on_event = any(
94+
isinstance(child, ast.FunctionDef) and child.name == "on_event"
95+
for child in node.body
96+
)
97+
return (True, handlers, has_on_event) if handlers else (True, None, has_on_event)
98+
return False, None, False
9099

91100

92101
class WebhookUploader:
@@ -101,17 +110,20 @@ def __init__(
101110
header: Optional[str],
102111
verbose: bool,
103112
handler_types: Optional[List[str]],
113+
event_matcher: Optional[List[str]],
104114
) -> None:
105115
self.kili = kili
106116
self.webhook_url = webhook_url
107117
self.plugin_name = plugin_name or self.webhook_url
108118
self.header = header
109119
self.verbose = verbose
110120
self.handler_types = handler_types
121+
self.event_matcher = event_matcher
111122

112123
def create_webhook(self) -> str:
113124
"""Create a webhook receiving Kili events."""
114125
variables = {
126+
"eventMatcher": self.event_matcher,
115127
"handlerTypes": self.handler_types,
116128
"pluginName": self.plugin_name,
117129
"webhookUrl": self.webhook_url,
@@ -125,6 +137,7 @@ def create_webhook(self) -> str:
125137
def update_webhook(self) -> str:
126138
"""Update a webhook receiving Kili events."""
127139
variables = {
140+
"eventMatcher": self.event_matcher,
128141
"handlerTypes": self.handler_types,
129142
"pluginName": self.plugin_name,
130143
"webhookUrl": self.webhook_url,
@@ -147,10 +160,12 @@ def __init__(
147160
plugin_name: Optional[str],
148161
verbose: bool,
149162
http_client: HttpClient,
163+
event_matcher: Optional[List[str]],
150164
) -> None:
151165
self.kili = kili
152166
self.plugin_path = Path(plugin_path)
153167
self.http_client = http_client
168+
self.event_matcher = event_matcher
154169

155170
if (not self.plugin_path.is_dir()) and (not self.plugin_path.is_file()):
156171
raise FileNotFoundError(
@@ -173,10 +188,16 @@ def _retrieve_plugin_src(self) -> List[Path]:
173188
raise FileNotFoundError(
174189
f"No main.py file in the provided folder: {self.plugin_path.absolute()}"
175190
)
176-
contains_handler, handler_types = check_file_contains_handler(file_path)
191+
contains_handler, handler_types, has_on_event = check_file_contains_handler(file_path)
177192
if not contains_handler:
178193
raise ValueError("PluginHandler class is not present in your main.py file.")
179194

195+
if has_on_event and not self.event_matcher:
196+
raise ValueError("Event matcher is required for plugins with on_event method.")
197+
198+
if handler_types and self.event_matcher:
199+
raise ValueError("Cannot have both handler types and event matcher.")
200+
180201
self.handler_types = handler_types
181202

182203
return list(self.plugin_path.glob("**/*.py"))
@@ -186,10 +207,16 @@ def _retrieve_plugin_src(self) -> List[Path]:
186207
if not check_file_is_py(file_path, self.verbose):
187208
raise ValueError("Wrong file format.")
188209

189-
contains_handler, handler_types = check_file_contains_handler(file_path)
210+
contains_handler, handler_types, has_on_event = check_file_contains_handler(file_path)
190211
if not contains_handler:
191212
raise ValueError("PluginHandler class is not present in your plugin file.")
192213

214+
if has_on_event and not self.event_matcher:
215+
raise ValueError("Event matcher is required for plugins with on_event method.")
216+
217+
if handler_types and self.event_matcher:
218+
raise ValueError("Cannot have both handler types and event matcher.")
219+
193220
self.handler_types = handler_types
194221

195222
return [file_path]
@@ -276,7 +303,11 @@ def _upload_script(self, is_updating_plugin: bool) -> None:
276303

277304
def _create_plugin_runner(self) -> Any:
278305
"""Create plugin's runner."""
279-
variables = {"pluginName": self.plugin_name, "handlerTypes": self.handler_types}
306+
variables = {
307+
"pluginName": self.plugin_name,
308+
"handlerTypes": self.handler_types,
309+
"eventMatcher": self.event_matcher,
310+
}
280311

281312
result = self.kili.graphql_client.execute(GQL_CREATE_PLUGIN_RUNNER, variables)
282313
return self.kili.format_result("data", result)
@@ -345,7 +376,11 @@ def create_plugin(self):
345376

346377
def _update_plugin_runner(self) -> Any:
347378
"""Update plugin's runner."""
348-
variables = {"pluginName": self.plugin_name, "handlerTypes": self.handler_types}
379+
variables = {
380+
"pluginName": self.plugin_name,
381+
"handlerTypes": self.handler_types,
382+
"eventMatcher": self.event_matcher,
383+
}
349384

350385
result = self.kili.graphql_client.execute(GQL_UPDATE_PLUGIN_RUNNER, variables)
351386
return self.kili.format_result("data", result)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from typing import Dict
2+
3+
from kili.plugins import PluginCore
4+
5+
6+
class PluginHandler(PluginCore):
7+
def on_event(self, payload: Dict) -> None:
8+
super().on_event(payload=payload)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from typing import Dict
2+
3+
from kili.plugins import PluginCore
4+
5+
6+
class PluginHandler(PluginCore):
7+
def on_event(self, payload: Dict) -> None:
8+
super().on_event(payload=payload)
9+
10+
def on_submit(self, label: Dict, asset_id: str) -> None:
11+
super().on_submit(label=label, asset_id=asset_id)
12+
13+
def on_review(self, label: Dict, asset_id: str) -> None:
14+
super().on_review(label=label, asset_id=asset_id)

0 commit comments

Comments
 (0)