planelet-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- planelet_sdk/__init__.py +52 -0
- planelet_sdk/planelet.py +105 -0
- planelet_sdk/py.typed +0 -0
- planelet_sdk/server.py +268 -0
- planelet_sdk/trigger.py +54 -0
- planelet_sdk/types.py +93 -0
- planelet_sdk-0.1.0.dist-info/METADATA +79 -0
- planelet_sdk-0.1.0.dist-info/RECORD +9 -0
- planelet_sdk-0.1.0.dist-info/WHEEL +4 -0
planelet_sdk/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from .planelet import Planelet
|
|
2
|
+
from .types import (
|
|
3
|
+
ActionContext,
|
|
4
|
+
CleanupContext,
|
|
5
|
+
HttpResponse,
|
|
6
|
+
Param,
|
|
7
|
+
ParamOption,
|
|
8
|
+
SetupContext,
|
|
9
|
+
WebhookContext,
|
|
10
|
+
WebhookError,
|
|
11
|
+
WebhookInfo,
|
|
12
|
+
WebhookRequest,
|
|
13
|
+
WebhookResult,
|
|
14
|
+
WebhookSkip,
|
|
15
|
+
)
|
|
16
|
+
from .trigger import TriggerBuilder
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_planelet(
|
|
20
|
+
*,
|
|
21
|
+
id: str,
|
|
22
|
+
label: str,
|
|
23
|
+
icon: str | None = None,
|
|
24
|
+
icon_url: str | None = None,
|
|
25
|
+
description: str | None = None,
|
|
26
|
+
) -> Planelet:
|
|
27
|
+
return Planelet(
|
|
28
|
+
id=id,
|
|
29
|
+
label=label,
|
|
30
|
+
icon=icon,
|
|
31
|
+
icon_url=icon_url,
|
|
32
|
+
description=description,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"create_planelet",
|
|
38
|
+
"ActionContext",
|
|
39
|
+
"CleanupContext",
|
|
40
|
+
"HttpResponse",
|
|
41
|
+
"Param",
|
|
42
|
+
"ParamOption",
|
|
43
|
+
"Planelet",
|
|
44
|
+
"SetupContext",
|
|
45
|
+
"TriggerBuilder",
|
|
46
|
+
"WebhookContext",
|
|
47
|
+
"WebhookError",
|
|
48
|
+
"WebhookInfo",
|
|
49
|
+
"WebhookRequest",
|
|
50
|
+
"WebhookResult",
|
|
51
|
+
"WebhookSkip",
|
|
52
|
+
]
|
planelet_sdk/planelet.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Awaitable
|
|
4
|
+
|
|
5
|
+
from .trigger import TriggerBuilder
|
|
6
|
+
from .types import ActionContext, Param
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ActionHandler = Callable[[ActionContext], Awaitable[dict[str, Any]]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _ActionDef:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
id: str,
|
|
16
|
+
*,
|
|
17
|
+
label: str,
|
|
18
|
+
description: str | None,
|
|
19
|
+
icon: str | None,
|
|
20
|
+
icon_url: str | None,
|
|
21
|
+
parameters: dict[str, Param],
|
|
22
|
+
handler: ActionHandler,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.id = id
|
|
25
|
+
self.label = label
|
|
26
|
+
self.description = description
|
|
27
|
+
self.icon = icon
|
|
28
|
+
self.icon_url = icon_url
|
|
29
|
+
self.parameters = parameters
|
|
30
|
+
self.handler = handler
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Planelet:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
id: str,
|
|
38
|
+
label: str,
|
|
39
|
+
icon: str | None = None,
|
|
40
|
+
icon_url: str | None = None,
|
|
41
|
+
description: str | None = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
self.id = id
|
|
44
|
+
self.label = label
|
|
45
|
+
self.icon = icon
|
|
46
|
+
self.icon_url = icon_url
|
|
47
|
+
self.description = description
|
|
48
|
+
|
|
49
|
+
self._actions: dict[str, _ActionDef] = {}
|
|
50
|
+
self._triggers: dict[str, TriggerBuilder] = {}
|
|
51
|
+
|
|
52
|
+
def action(
|
|
53
|
+
self,
|
|
54
|
+
id: str,
|
|
55
|
+
*,
|
|
56
|
+
label: str,
|
|
57
|
+
description: str | None = None,
|
|
58
|
+
icon: str | None = None,
|
|
59
|
+
icon_url: str | None = None,
|
|
60
|
+
parameters: dict[str, Param] | None = None,
|
|
61
|
+
) -> Callable[[ActionHandler], ActionHandler]:
|
|
62
|
+
params = parameters or {}
|
|
63
|
+
|
|
64
|
+
def decorator(fn: ActionHandler) -> ActionHandler:
|
|
65
|
+
self._actions[id] = _ActionDef(
|
|
66
|
+
id=id,
|
|
67
|
+
label=label,
|
|
68
|
+
description=description,
|
|
69
|
+
icon=icon,
|
|
70
|
+
icon_url=icon_url,
|
|
71
|
+
parameters=params,
|
|
72
|
+
handler=fn,
|
|
73
|
+
)
|
|
74
|
+
return fn
|
|
75
|
+
|
|
76
|
+
return decorator
|
|
77
|
+
|
|
78
|
+
def trigger(
|
|
79
|
+
self,
|
|
80
|
+
id: str,
|
|
81
|
+
*,
|
|
82
|
+
label: str,
|
|
83
|
+
description: str | None = None,
|
|
84
|
+
icon: str | None = None,
|
|
85
|
+
icon_url: str | None = None,
|
|
86
|
+
parameters: dict[str, Param] | None = None,
|
|
87
|
+
) -> TriggerBuilder:
|
|
88
|
+
builder = TriggerBuilder(
|
|
89
|
+
id,
|
|
90
|
+
label=label,
|
|
91
|
+
description=description,
|
|
92
|
+
icon=icon,
|
|
93
|
+
icon_url=icon_url,
|
|
94
|
+
parameters=parameters,
|
|
95
|
+
)
|
|
96
|
+
self._triggers[id] = builder
|
|
97
|
+
return builder
|
|
98
|
+
|
|
99
|
+
def listen(self, port: int = 3000) -> None:
|
|
100
|
+
from .server import build_app
|
|
101
|
+
|
|
102
|
+
import uvicorn
|
|
103
|
+
|
|
104
|
+
app = build_app(self)
|
|
105
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
planelet_sdk/py.typed
ADDED
|
File without changes
|
planelet_sdk/server.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from typing import Any, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI, Request
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
|
|
9
|
+
from .types import (
|
|
10
|
+
ActionContext,
|
|
11
|
+
CleanupContext,
|
|
12
|
+
HttpResponse,
|
|
13
|
+
SetupContext,
|
|
14
|
+
WebhookContext,
|
|
15
|
+
WebhookError,
|
|
16
|
+
WebhookInfo,
|
|
17
|
+
WebhookRequest,
|
|
18
|
+
WebhookResult,
|
|
19
|
+
WebhookSkip,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from .planelet import Planelet
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _serialize_params(parameters: dict[str, Any]) -> list[dict[str, Any]]:
|
|
27
|
+
result = []
|
|
28
|
+
for param_id, param in parameters.items():
|
|
29
|
+
entry: dict[str, Any] = {
|
|
30
|
+
"id": param_id,
|
|
31
|
+
"label": param.label,
|
|
32
|
+
"type": param.type,
|
|
33
|
+
}
|
|
34
|
+
if param.description is not None:
|
|
35
|
+
entry["description"] = param.description
|
|
36
|
+
if param.required:
|
|
37
|
+
entry["required"] = True
|
|
38
|
+
if param.default is not None:
|
|
39
|
+
entry["default"] = param.default
|
|
40
|
+
if param.options is not None:
|
|
41
|
+
entry["options"] = [
|
|
42
|
+
{"label": o.label, "value": o.value} for o in param.options
|
|
43
|
+
]
|
|
44
|
+
result.append(entry)
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _serialize_http_response(resp: HttpResponse | None) -> dict[str, Any] | None:
|
|
49
|
+
if resp is None:
|
|
50
|
+
return None
|
|
51
|
+
out: dict[str, Any] = {"status": resp.status}
|
|
52
|
+
if resp.headers:
|
|
53
|
+
out["headers"] = resp.headers
|
|
54
|
+
if resp.body is not None:
|
|
55
|
+
out["body"] = resp.body
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def build_app(planelet: Planelet) -> FastAPI:
|
|
60
|
+
app = FastAPI(title=planelet.label)
|
|
61
|
+
|
|
62
|
+
@app.get("/manifest")
|
|
63
|
+
async def manifest() -> dict[str, Any]:
|
|
64
|
+
actions = []
|
|
65
|
+
for action_def in planelet._actions.values():
|
|
66
|
+
entry: dict[str, Any] = {
|
|
67
|
+
"id": action_def.id,
|
|
68
|
+
"label": action_def.label,
|
|
69
|
+
"parameters": _serialize_params(action_def.parameters),
|
|
70
|
+
}
|
|
71
|
+
if action_def.description:
|
|
72
|
+
entry["description"] = action_def.description
|
|
73
|
+
if action_def.icon:
|
|
74
|
+
entry["icon"] = action_def.icon
|
|
75
|
+
if action_def.icon_url:
|
|
76
|
+
entry["iconUrl"] = action_def.icon_url
|
|
77
|
+
actions.append(entry)
|
|
78
|
+
|
|
79
|
+
triggers = []
|
|
80
|
+
for trigger in planelet._triggers.values():
|
|
81
|
+
entry = {
|
|
82
|
+
"id": trigger.id,
|
|
83
|
+
"label": trigger.label,
|
|
84
|
+
"parameters": _serialize_params(trigger.parameters),
|
|
85
|
+
"webhook": {"setup": "plugin"},
|
|
86
|
+
}
|
|
87
|
+
if trigger.description:
|
|
88
|
+
entry["description"] = trigger.description
|
|
89
|
+
if trigger.icon:
|
|
90
|
+
entry["icon"] = trigger.icon
|
|
91
|
+
if trigger.icon_url:
|
|
92
|
+
entry["iconUrl"] = trigger.icon_url
|
|
93
|
+
triggers.append(entry)
|
|
94
|
+
|
|
95
|
+
result: dict[str, Any] = {
|
|
96
|
+
"id": planelet.id,
|
|
97
|
+
"label": planelet.label,
|
|
98
|
+
"actions": actions,
|
|
99
|
+
"triggers": triggers,
|
|
100
|
+
}
|
|
101
|
+
if planelet.icon:
|
|
102
|
+
result["icon"] = planelet.icon
|
|
103
|
+
if planelet.icon_url:
|
|
104
|
+
result["iconUrl"] = planelet.icon_url
|
|
105
|
+
if planelet.description:
|
|
106
|
+
result["description"] = planelet.description
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
@app.post("/actions/{action_id}/execute")
|
|
110
|
+
async def execute_action(action_id: str, request: Request) -> JSONResponse:
|
|
111
|
+
action_def = planelet._actions.get(action_id)
|
|
112
|
+
if not action_def:
|
|
113
|
+
return JSONResponse(
|
|
114
|
+
{"success": False, "error": f'Action "{action_id}" not found'},
|
|
115
|
+
status_code=404,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
body = await request.json()
|
|
119
|
+
ctx = ActionContext(
|
|
120
|
+
parameters=body.get("parameters", {}),
|
|
121
|
+
input=body.get("input"),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
data = await action_def.handler(ctx)
|
|
126
|
+
return JSONResponse({"success": True, "data": data})
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
return JSONResponse(
|
|
129
|
+
{"success": False, "error": str(exc)},
|
|
130
|
+
status_code=500,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@app.post("/triggers/{trigger_id}/setup")
|
|
134
|
+
async def setup_trigger(trigger_id: str, request: Request) -> JSONResponse:
|
|
135
|
+
trigger = planelet._triggers.get(trigger_id)
|
|
136
|
+
if not trigger:
|
|
137
|
+
return JSONResponse(
|
|
138
|
+
{"success": False, "error": f'Trigger "{trigger_id}" not found'},
|
|
139
|
+
status_code=404,
|
|
140
|
+
)
|
|
141
|
+
if not trigger._setup:
|
|
142
|
+
return JSONResponse(
|
|
143
|
+
{"success": False, "error": f'Trigger "{trigger_id}" has no setup handler'},
|
|
144
|
+
status_code=501,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
body = await request.json()
|
|
148
|
+
webhook_data = body.get("webhook", {})
|
|
149
|
+
ctx = SetupContext(
|
|
150
|
+
parameters=body.get("parameters", {}),
|
|
151
|
+
webhook=WebhookInfo(
|
|
152
|
+
url=webhook_data.get("url", ""),
|
|
153
|
+
secret=webhook_data.get("secret"),
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
metadata = await trigger._setup(ctx)
|
|
159
|
+
result: dict[str, Any] = {"success": True}
|
|
160
|
+
if metadata:
|
|
161
|
+
result["metadata"] = metadata
|
|
162
|
+
return JSONResponse(result)
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
return JSONResponse(
|
|
165
|
+
{"success": False, "error": str(exc)},
|
|
166
|
+
status_code=500,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@app.post("/triggers/{trigger_id}/cleanup")
|
|
170
|
+
async def cleanup_trigger(trigger_id: str, request: Request) -> JSONResponse:
|
|
171
|
+
trigger = planelet._triggers.get(trigger_id)
|
|
172
|
+
if not trigger:
|
|
173
|
+
return JSONResponse(
|
|
174
|
+
{"success": False, "error": f'Trigger "{trigger_id}" not found'},
|
|
175
|
+
status_code=404,
|
|
176
|
+
)
|
|
177
|
+
if not trigger._cleanup:
|
|
178
|
+
return JSONResponse(
|
|
179
|
+
{"success": False, "error": f'Trigger "{trigger_id}" has no cleanup handler'},
|
|
180
|
+
status_code=501,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
body = await request.json()
|
|
184
|
+
ctx = CleanupContext(
|
|
185
|
+
parameters=body.get("parameters", {}),
|
|
186
|
+
metadata=body.get("metadata"),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
await trigger._cleanup(ctx)
|
|
191
|
+
return JSONResponse({"success": True})
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
return JSONResponse(
|
|
194
|
+
{"success": False, "error": str(exc)},
|
|
195
|
+
status_code=500,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@app.post("/triggers/{trigger_id}/webhook")
|
|
199
|
+
async def handle_webhook(trigger_id: str, request: Request) -> JSONResponse:
|
|
200
|
+
trigger = planelet._triggers.get(trigger_id)
|
|
201
|
+
if not trigger:
|
|
202
|
+
return JSONResponse(
|
|
203
|
+
{"success": False, "error": f'Trigger "{trigger_id}" not found'},
|
|
204
|
+
status_code=404,
|
|
205
|
+
)
|
|
206
|
+
if not trigger._webhook:
|
|
207
|
+
return JSONResponse(
|
|
208
|
+
{"success": False, "error": f'Trigger "{trigger_id}" has no webhook handler'},
|
|
209
|
+
status_code=501,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
body = await request.json()
|
|
213
|
+
req_data = body.get("request", {})
|
|
214
|
+
raw_body_b64 = req_data.get("rawBodyBase64", "")
|
|
215
|
+
raw_body = base64.b64decode(raw_body_b64) if raw_body_b64 else b""
|
|
216
|
+
|
|
217
|
+
ctx = WebhookContext(
|
|
218
|
+
parameters=body.get("parameters", {}),
|
|
219
|
+
metadata=body.get("metadata"),
|
|
220
|
+
request=WebhookRequest(
|
|
221
|
+
method=req_data.get("method", "POST"),
|
|
222
|
+
headers=req_data.get("headers", {}),
|
|
223
|
+
query=req_data.get("query"),
|
|
224
|
+
raw_body=raw_body,
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
result = await trigger._webhook(ctx)
|
|
230
|
+
except Exception as exc:
|
|
231
|
+
return JSONResponse(
|
|
232
|
+
{"success": False, "error": str(exc)},
|
|
233
|
+
status_code=500,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if isinstance(result, WebhookResult):
|
|
237
|
+
resp: dict[str, Any] = {
|
|
238
|
+
"success": True,
|
|
239
|
+
"emit": True,
|
|
240
|
+
"eventType": result.event_type,
|
|
241
|
+
"payload": result.payload,
|
|
242
|
+
}
|
|
243
|
+
http_resp = _serialize_http_response(result.response)
|
|
244
|
+
if http_resp:
|
|
245
|
+
resp["response"] = http_resp
|
|
246
|
+
return JSONResponse(resp)
|
|
247
|
+
|
|
248
|
+
if isinstance(result, WebhookSkip):
|
|
249
|
+
resp = {"success": True, "emit": False}
|
|
250
|
+
if result.reason:
|
|
251
|
+
resp["reason"] = result.reason
|
|
252
|
+
http_resp = _serialize_http_response(result.response)
|
|
253
|
+
if http_resp:
|
|
254
|
+
resp["response"] = http_resp
|
|
255
|
+
return JSONResponse(resp)
|
|
256
|
+
|
|
257
|
+
if isinstance(result, WebhookError):
|
|
258
|
+
err_resp: dict[str, Any] = {"success": False, "error": result.error}
|
|
259
|
+
if result.status is not None:
|
|
260
|
+
err_resp["status"] = result.status
|
|
261
|
+
return JSONResponse(err_resp, status_code=result.status or 500)
|
|
262
|
+
|
|
263
|
+
return JSONResponse(
|
|
264
|
+
{"success": False, "error": "Webhook handler returned invalid type"},
|
|
265
|
+
status_code=500,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return app
|
planelet_sdk/trigger.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Awaitable
|
|
4
|
+
|
|
5
|
+
from .types import (
|
|
6
|
+
CleanupContext,
|
|
7
|
+
Param,
|
|
8
|
+
SetupContext,
|
|
9
|
+
WebhookContext,
|
|
10
|
+
WebhookError,
|
|
11
|
+
WebhookResult,
|
|
12
|
+
WebhookSkip,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
SetupHandler = Callable[[SetupContext], Awaitable[dict[str, Any] | None]]
|
|
16
|
+
CleanupHandler = Callable[[CleanupContext], Awaitable[None]]
|
|
17
|
+
WebhookHandler = Callable[
|
|
18
|
+
[WebhookContext], Awaitable[WebhookResult | WebhookSkip | WebhookError]
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TriggerBuilder:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
id: str,
|
|
26
|
+
*,
|
|
27
|
+
label: str,
|
|
28
|
+
description: str | None = None,
|
|
29
|
+
icon: str | None = None,
|
|
30
|
+
icon_url: str | None = None,
|
|
31
|
+
parameters: dict[str, Param] | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
self.id = id
|
|
34
|
+
self.label = label
|
|
35
|
+
self.description = description
|
|
36
|
+
self.icon = icon
|
|
37
|
+
self.icon_url = icon_url
|
|
38
|
+
self.parameters = parameters or {}
|
|
39
|
+
|
|
40
|
+
self._setup: SetupHandler | None = None
|
|
41
|
+
self._cleanup: CleanupHandler | None = None
|
|
42
|
+
self._webhook: WebhookHandler | None = None
|
|
43
|
+
|
|
44
|
+
def on_setup(self, fn: SetupHandler) -> SetupHandler:
|
|
45
|
+
self._setup = fn
|
|
46
|
+
return fn
|
|
47
|
+
|
|
48
|
+
def on_cleanup(self, fn: CleanupHandler) -> CleanupHandler:
|
|
49
|
+
self._cleanup = fn
|
|
50
|
+
return fn
|
|
51
|
+
|
|
52
|
+
def on_webhook(self, fn: WebhookHandler) -> WebhookHandler:
|
|
53
|
+
self._webhook = fn
|
|
54
|
+
return fn
|
planelet_sdk/types.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Param:
|
|
9
|
+
label: str
|
|
10
|
+
type: str # "string" | "text" | "number" | "bool" | "select" | "object"
|
|
11
|
+
description: str | None = None
|
|
12
|
+
required: bool = False
|
|
13
|
+
default: Any = None
|
|
14
|
+
options: list[ParamOption] | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ParamOption:
|
|
19
|
+
label: str
|
|
20
|
+
value: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# --- Contexts passed to handlers ---
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ActionContext:
|
|
28
|
+
parameters: dict[str, Any]
|
|
29
|
+
input: Any | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class WebhookInfo:
|
|
34
|
+
url: str
|
|
35
|
+
secret: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class SetupContext:
|
|
40
|
+
parameters: dict[str, Any]
|
|
41
|
+
webhook: WebhookInfo = field(default_factory=lambda: WebhookInfo(url=""))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class CleanupContext:
|
|
46
|
+
parameters: dict[str, Any]
|
|
47
|
+
metadata: dict[str, Any] | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class WebhookRequest:
|
|
52
|
+
method: str
|
|
53
|
+
headers: dict[str, list[str]]
|
|
54
|
+
query: dict[str, list[str]] | None = None
|
|
55
|
+
raw_body: bytes = b""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class WebhookContext:
|
|
60
|
+
parameters: dict[str, Any]
|
|
61
|
+
metadata: dict[str, Any] | None = None
|
|
62
|
+
request: WebhookRequest = field(
|
|
63
|
+
default_factory=lambda: WebhookRequest(method="POST", headers={})
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# --- Result types returned by handlers ---
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class HttpResponse:
|
|
72
|
+
status: int = 200
|
|
73
|
+
headers: dict[str, str] | None = None
|
|
74
|
+
body: str | None = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class WebhookResult:
|
|
79
|
+
event_type: str
|
|
80
|
+
payload: Any = None
|
|
81
|
+
response: HttpResponse | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class WebhookSkip:
|
|
86
|
+
reason: str | None = None
|
|
87
|
+
response: HttpResponse | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class WebhookError:
|
|
92
|
+
error: str
|
|
93
|
+
status: int | None = None
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: planelet-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for building SuperPlane planelets
|
|
5
|
+
Author: ThatXliner
|
|
6
|
+
Author-email: ThatXliner <thatxliner@gmail.com>
|
|
7
|
+
Requires-Dist: fastapi>=0.100.0
|
|
8
|
+
Requires-Dist: uvicorn[standard]>=0.20.0
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# SuperPlane Planelet SDK for Python
|
|
13
|
+
|
|
14
|
+
Build custom SuperPlane integrations in Python. Define actions and webhook triggers with decorators, and the SDK serves the full Planelet protocol over HTTP.
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### 1. Create a new project
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
mkdir my-planelet && cd my-planelet
|
|
22
|
+
python -m venv .venv && source .venv/bin/activate
|
|
23
|
+
pip install superplane-planelet-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Write your Planelet
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
# main.py
|
|
30
|
+
from superplane import create_planelet, Param, ActionContext
|
|
31
|
+
|
|
32
|
+
planelet = create_planelet(
|
|
33
|
+
id="my-planelet",
|
|
34
|
+
label="My Planelet",
|
|
35
|
+
description="Does useful things",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@planelet.action(
|
|
39
|
+
"hello",
|
|
40
|
+
label="Say Hello",
|
|
41
|
+
description="Generates a greeting",
|
|
42
|
+
parameters={
|
|
43
|
+
"name": Param(label="Name", type="string", required=True),
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
async def hello(ctx: ActionContext):
|
|
47
|
+
return {"message": f"Hello, {ctx.parameters['name']}!"}
|
|
48
|
+
|
|
49
|
+
planelet.listen(3001)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Run it
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python main.py
|
|
56
|
+
# Uvicorn running on http://0.0.0.0:3001
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4. Connect to SuperPlane
|
|
60
|
+
|
|
61
|
+
1. In SuperPlane, add a new **Planelets** integration
|
|
62
|
+
2. Set **Server URL** to your Planelet server's address
|
|
63
|
+
3. Optionally set an **Auth Token**
|
|
64
|
+
4. Save — SuperPlane fetches your manifest and the integration goes ready
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
See [PLANELET-DOCS.md](PLANELET-DOCS.md) for the full SDK reference, trigger/webhook guide, and protocol details.
|
|
69
|
+
|
|
70
|
+
## Example
|
|
71
|
+
|
|
72
|
+
See [`examples/quotes.py`](examples/quotes.py) for a complete example with two actions. Run it:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd examples
|
|
76
|
+
python quotes.py
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then connect SuperPlane to `http://localhost:3001`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
planelet_sdk/__init__.py,sha256=7wDPuHMmI26-lB_-FYKsnB59JPEtZ2kpEsiynmNeFRk,933
|
|
2
|
+
planelet_sdk/planelet.py,sha256=0CdW6P-DAFLqE4PSiKbcSPG25wrM8rHK-hPox8SbT9c,2631
|
|
3
|
+
planelet_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
planelet_sdk/server.py,sha256=QzDr2vqHYbH-VEaeBG-dW2jG8hFhAqBsrXclFxZfuM8,9019
|
|
5
|
+
planelet_sdk/trigger.py,sha256=BXIrMGh6bQSXsMid0hO21ANgAmIpx1eC_wHfcDgodZQ,1412
|
|
6
|
+
planelet_sdk/types.py,sha256=VWVsiakShC0uBNn09GGthHbOk_nUbVblNI1avoWbq-U,1725
|
|
7
|
+
planelet_sdk-0.1.0.dist-info/WHEEL,sha256=f5fWSvWsg5Knq5GWa6t1nJIug0Tqo69GqAWD_9LbBKw,81
|
|
8
|
+
planelet_sdk-0.1.0.dist-info/METADATA,sha256=OE_QLOAtU8pwhRKe3RbgZ7zPz_SKx8JD8T5Vl6zPJ04,1903
|
|
9
|
+
planelet_sdk-0.1.0.dist-info/RECORD,,
|