Skip to content

Commit 733b024

Browse files
authored
Merge pull request #2019 from kili-technology/revert-2017-feature/lab-3584-aakd-i-see-onsubmit-and-onreview-handlers-removed-from
Revert "feat(lab-3584): update documentation to fully remove onSubmit and onR…"
2 parents d8abecb + 9c0685c commit 733b024

11 files changed

Lines changed: 748 additions & 62 deletions

File tree

docs/sdk/plugins.md

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,15 @@ plugin_folder
1919
|__ helper.py
2020
```
2121

22+
The plugin you are going to upload has to contain a `class PluginHandler(PluginCore)` (in the case of the module type plugin it has to be inside `main.py`) that implements two methods for the different types of events:
2223

23-
> **Note**: `on_submit` and `on_review` handlers are deprecated. Please use `on_event` instead. If your plugin was already uploaded with thoses handlers, it will still work. If you want the same behavior as `on_submit` and `on_review`, when you upload your plugin, you can use `event_matcher=["labels.created.submit"]` for `on_submit` and `event_matcher=["labels.created.review"]` for `on_review` events.
24+
- `on_submit`
25+
- `on_review`
2426

27+
These methods have a predefined set of parameters:
2528

26-
The plugin you are going to upload has to contain a `class PluginHandler(PluginCore)` (in the case of the module type plugin it has to be inside `main.py`) that implements one method for the different types of events:
27-
28-
- `on_event`
29-
30-
These methods have a predefined set of parameter:
31-
32-
- the `payload` submitted (a dictionary containing the fields for your plugins)
33-
- `payload.event` the name of the event (ex: for submit : `labels.created.submit`, for review `labels.created.review`)
34-
- `payload.label` the label containing the fields of the GraphQL type [Label](https://api-docs.kili-technology.com/types/objects/label/)
29+
- the `label` submitted (a dictionary containing the fields of the GraphQL type [Label](https://api-docs.kili-technology.com/types/objects/label/))
30+
- the `asset_id` of the asset labeled
3531

3632
You can add custom methods in your class as well.
3733

@@ -57,18 +53,12 @@ class PluginHandler(PluginCore):
5753
def custom_method(self):
5854
# Do something...
5955

60-
def on_event(self, payload: dict) -> None: # This can replace on_review method
56+
def on_review(self, label: Dict, asset_id: str) -> None:
6157
"""Dedicated handler for Review action"""
62-
event = payload.get("event")
63-
64-
if event == 'labels.created.review':
6558
# Do something...
6659

67-
def on_event(self, payload: dict) -> None: # This can replace on_submit method
60+
def on_submit(self, label: Dict, asset_id: str) -> None:
6861
"""Dedicated handler for Submit action"""
69-
event = payload.get("event")
70-
71-
if event == 'labels.created.submit':
7262
# Do something...
7363
```
7464

@@ -95,22 +85,15 @@ def custom_function(label: Dict, logger: Logger):
9585
class PluginHandler(PluginCore):
9686
"""Custom plugin"""
9787

98-
def on_event(self, payload: dict) -> None:
88+
def on_submit(self, label: Dict, asset_id: str) -> None:
9989
"""Dedicated handler for Submit action"""
100-
event = payload.get("event")
101-
if event == 'labels.created.submit':
102-
self.logger.info("On Submit called")
103-
label = payload["label"]
104-
custom_function(label, self.logger)
90+
self.logger.info("On Submit called")
91+
custom_function(label, self.logger)
10592
```
10693

10794
## Model for Plugins
10895

10996
::: kili.services.plugins.model.PluginCore
110-
options:
111-
filters:
112-
- "!^on_review$"
113-
- "!^on_submit$"
11497

11598
## Queries
11699

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
<!-- FILE AUTO GENERATED BY docs/utils.py DO NOT EDIT DIRECTLY -->
2+
<a href="https://colab.research.google.com/github/kili-technology/kili-python-sdk/blob/main/recipes/plugins_development.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>
3+
4+
# How to develop and test a Kili plugin
5+
6+
## Preliminary
7+
8+
This notebook will teach you how to build your first plugin.
9+
10+
A plugin is an uploaded Python script triggered by an event that you define.
11+
12+
For instance, you can trigger a plugin when a labeler clicks on **Submit** with the `on_submit` handler.
13+
14+
The plugin should have different methods for the different types of events:
15+
16+
- `on_submit`
17+
- `on_review`
18+
19+
These methods have a predefined set of parameters:
20+
21+
- the `label` submitted
22+
- the `asset_id` of the asset labeled
23+
24+
Some attributes are available in the class:
25+
26+
- `self.kili`
27+
- `self.project_id`
28+
29+
Therefore, the skeleton of the plugin should look like this:
30+
31+
```python
32+
from kili.plugins import PluginCore
33+
from typing import Dict
34+
import numpy as np
35+
36+
class PluginHandler(PluginCore):
37+
"""Custom plugin"""
38+
39+
def on_review(self, label: Dict, asset_id: str) -> None:
40+
"""Dedicated handler for Review action"""
41+
# Do something...
42+
43+
def on_submit(self, label: Dict, asset_id: str) -> None:
44+
"""Dedicated handler for Submit action"""
45+
# Do something...
46+
```
47+
48+
Do not hesitate to reach out to us if you need more.
49+
50+
**NB: The plugin capabilities of Kili are under active development, and compatible with version 2.125.2 and later of Kili. Don't hesitate to reach out via Github or Kili support to provide feedback.**
51+
52+
## Instantiate Kili
53+
54+
55+
```python
56+
%pip install kili
57+
```
58+
59+
60+
```python
61+
%load_ext autoreload
62+
%autoreload 2
63+
64+
65+
from kili.client import Kili
66+
67+
kili = Kili(
68+
# api_endpoint="https://cloud.kili-technology.com/api/label/v2/graphql",
69+
# the line above can be uncommented and changed if you are working with an on-premise version of Kili
70+
)
71+
```
72+
73+
## Develop your plugin
74+
75+
The first step is to define the functions that will be called when the event is triggered. You will be able to iterate on these functions locally (more on that in the next section).
76+
77+
The plugin can be defined in two ways: a single `.py` file with everything inside or a module (folder containing multiple `.py` files). In the case of the module type, a file named `main.py` needs to be at the root of the folder and will serve as the entrypoint.
78+
79+
### 1. First option - Plugin defined in a single file
80+
81+
This cell should be the contents of the `.py` file that you will upload as a plugin at the end.
82+
83+
**This file should define the `PluginHandler` class that will contain the proper methods.**
84+
85+
We recommend using a modern IDE like VScode to get type hints and autocompletion on the methods.
86+
87+
88+
```python
89+
import numpy as np
90+
91+
from kili.plugins import PluginCore
92+
93+
94+
def custom_function(label: dict):
95+
label_id = label.get("id")
96+
print(f"My custom function for review of label with id {label_id}")
97+
98+
99+
class PluginHandler(PluginCore):
100+
"""Custom plugin instance"""
101+
102+
def custom_method(self, project_id, label_id):
103+
print(f"custom_method called for label {label_id}")
104+
random_seed = np.random.random(1)[0]
105+
if random_seed > 0.5:
106+
self.logger.warning("Generating issue")
107+
# Use kili for actions with self.kili
108+
self.kili.create_issues(
109+
project_id=project_id,
110+
label_id_array=[label_id],
111+
text_array=["Random issue generated for this label"],
112+
)
113+
114+
def on_review(self, label: dict, asset_id: str) -> None:
115+
"""Dedicated handler for Review action"""
116+
custom_function(label)
117+
118+
def on_submit(self, label: dict, asset_id: str) -> None:
119+
"""Dedicated handler for Submit action"""
120+
print("On submit called")
121+
122+
project_id = self.project_id
123+
label_id = label.get("id")
124+
125+
self.custom_method(project_id, label_id)
126+
```
127+
128+
### 2. Second option - Plugin defined in a folder
129+
130+
As said previously, the structure of the folder can be the following (the only constraint being the presence of the `main.py` file):
131+
```
132+
plugin_folder
133+
|__ main.py
134+
|__ other_file.py
135+
|__ requirements.txt
136+
|
137+
|___helpers
138+
|__ helper.py
139+
```
140+
141+
You can notice that you can also include a `requirements.txt` file in the folder and the necessary packages will be installed with your plugin. Don't forget to add them, since the plugin could work on your machine if you have them installed, but it won't be possible to create the plugin if there are missing dependencies.
142+
143+
**Note:** The `requirements.txt` file can only be included for the SaaS version of the Kili platform, for on-premise deployments there is a pre-defined list of packages that can be used. For more details, see the [documentation of plugins](https://python-sdk-docs.kili-technology.com/latest/sdk/plugins/)
144+
145+
**Important: The main.py file need to have the same skeleton as the plugin defined in a single file (presence of the class `PluginHandler`), the difference being that it can import and call functions defined in other files**
146+
147+
Depending on where the folder is stored, there are two ways to import the plugin in order to test it:
148+
149+
- The first way is to use a relative import (having the plugin folder and the notebook in the same folder). It is simpler and we recommend it as it will also allow the IDE to detect the correct methods and propose hints and autocompletion.
150+
- The second is to use an absolute path to the plugin folder
151+
152+
#### 2.1 Relative import
153+
154+
155+
```python
156+
# Here replace 'plugin_folder' with the actual name of the folder
157+
from plugin_folder.main import PluginHandler
158+
```
159+
160+
#### 2.2 Absolute path import
161+
162+
163+
```python
164+
import sys
165+
from pathlib import Path
166+
167+
# Input the path to the plugin folder (it should include the folder), for example '/path/to/plugin_folder'
168+
plugin_path = "<INSERT PATH TO PLUGIN FOLDER>"
169+
170+
module_path = str(Path(plugin_path).parent.absolute())
171+
172+
# We are inserting the path in the system PATH to be able to import the module in the next line
173+
sys.path.insert(0, module_path)
174+
175+
# In the next line replace 'plugin_folder' with the actual name of the folder
176+
from plugin_folder.main import PluginHandler
177+
```
178+
179+
### Testing the plugin locally
180+
181+
In this we will show you how to test your plugin locally before uploading it.
182+
183+
184+
```python
185+
project_id = "<PROJECT ID>"
186+
```
187+
188+
Instantiate the plugin:
189+
190+
191+
```python
192+
my_plugin_instance = PluginHandler(kili, project_id)
193+
194+
195+
def get_label(label_id, project_id):
196+
"""Function to get the object Label with the same keys as it will be in the plugin"""
197+
label = list(
198+
kili.labels(
199+
project_id=project_id,
200+
label_id=label_id,
201+
fields=["id", "jsonResponse", "author.id", "labelType", "createdAt", "secondsToLabel"],
202+
)
203+
)[0]
204+
205+
label["authorId"] = label["author"]["id"]
206+
del label["author"]
207+
return label
208+
```
209+
210+
### Test the plugin run
211+
212+
If you already have a test project with labels added, you can directly use the IDs of these labels (see the following cell). Otherwise, you can follow the *plugins_example.ipynb* notebook to create a new project and then upload an asset with an associated label.
213+
214+
215+
```python
216+
asset_id = "<YOUR_ASSET_ID>"
217+
label_id = "<YOUR_LABEL_ID>"
218+
```
219+
220+
221+
```python
222+
label = get_label(label_id=label_id, project_id=project_id)
223+
224+
my_plugin_instance.on_submit(label=label, asset_id=asset_id)
225+
```
226+
227+
### Test the plugin run on Kili
228+
229+
When you finish debugging the code, you may want to upload it directly into Kili.
230+
231+
Note that you might get an error if the plugin name already exists in your Kili organization.
232+
233+
234+
```python
235+
path_to_plugin = "path/to/my/plugin.py"
236+
plugin_name = "My first kili plugin"
237+
```
238+
239+
240+
```python
241+
from kili.exceptions import GraphQLError
242+
243+
try:
244+
kili.upload_plugin(path_to_plugin, plugin_name)
245+
except GraphQLError as error:
246+
print(str(error))
247+
```
248+
249+
Plugins must be activated in the project that you want them to run in. Be careful with production projects: your custom workflows or rules will also be applied
250+
251+
252+
```python
253+
kili.activate_plugin_on_project(plugin_name, project_id=project_id)
254+
```
255+
256+
## Monitoring the plugin
257+
258+
Plugin creation takes some time (around 5 minutes). The plugin will begin to run only after it's been fully created (if labeling events are to be triggered on this project).
259+
260+
Additionally, you can get the logs of the runs:
261+
262+
263+
```python
264+
kili.get_plugin_logs(project_id=project_id, plugin_name=plugin_name)
265+
```
266+
267+
You can set custom date rules for filtering your logs:
268+
269+
270+
```python
271+
from datetime import date, datetime
272+
273+
dt = date.today() # You can change this date if needed
274+
start_date = datetime.combine(dt, datetime.min.time())
275+
276+
kili.get_plugin_logs(project_id=project_id, plugin_name=plugin_name, start_date=start_date)
277+
```
278+
279+
## Managing your plugin
280+
281+
There are several other methods to manage your plugins and their lifecycle. To find out more, check the plugins [tutorials](https://python-sdk-docs.kili-technology.com/latest/tutorials).

docs/sdk/tutorials/plugins_example.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ This project has one job of bounding box creation with two categories.
9898

9999
With our plugin, we want to make sure that the labelers don't create more than one bounding box of category A.
100100

101+
To iterate on the plugin code, you can refer to the plugins_development.ipynb notebook.
101102

102103
## Step 3: Write the plugin
103104

docs/sdk/tutorials/plugins_library.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
In this section you can find multiple examples of use-cases where our system of plugins can help you in the Kili projects.
66

7-
You can also refer to our [tutorial](./plugins_example.md) to develop your plugin and iterate on it locally, before uploading the final version to Kili.
7+
You can also refer to our [tutorial](./plugins_development.md) to develop your plugin and iterate on it locally, before uploading the final version to Kili.
88

99
For further information concerning the Kili plugins, refer to the [Reference page](../plugins.md)
1010

mkdocs.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ nav:
4444
- PDF Assets: sdk/tutorials/importing_pdf_assets.md
4545
- Rich Text Assets: sdk/tutorials/import_text_assets.md
4646
- Multi-Layer Geospatial Assets: sdk/tutorials/importing_multilayer_geospatial_assets.md
47-
- LLM Static Assets: sdk/tutorials/llm_static.md
47+
- LLM Static Assets : sdk/tutorials/llm_static.md
4848
- Importing Labels:
4949
- Importing Labels: sdk/tutorials/importing_labels.md
5050
- OpenAI NER Pre-annotations: sdk/tutorials/ner_pre_annotations_openai.md
@@ -64,7 +64,8 @@ nav:
6464
- Parsing Labels: sdk/tutorials/label_parsing.md
6565
- LLM Dynamic Projects: sdk/tutorials/llm_dynamic.md
6666
- Setting Up Plugins:
67-
- Developing Plugins: sdk/tutorials/plugins_example.md
67+
- Developing Plugins: sdk/tutorials/plugins_development.md
68+
- Plugin Example - Programmatic QA: sdk/tutorials/plugins_example.md
6869
- Plugins Library: sdk/tutorials/plugins_library.md
6970
- Webhooks: sdk/tutorials/webhooks_example.md
7071
- Integrations:

0 commit comments

Comments
 (0)