Plotune SDK for developing extensions
Project description
Plotune SDK
Plotune SDK is a lightweight Python toolkit for developing modular extensions that seamlessly integrate with the Plotune Core system.
It provides a unified environment to build, serve, and manage extension logic — including REST APIs, WebSocket handlers, and runtime lifecycle management — all with minimal boilerplate.
Features
- FastAPI-based server — automatically starts a local HTTP/WebSocket server for your extension
- Event-driven design — register logic with decorators like
@server.on_event()or@server.on_ws() - Core communication — built-in client for registration and heartbeat with Plotune Core
- Runtime management — tray-based control with start/stop/kill functionality
- Cross-platform logging — lightweight rotating file logger without external dependencies
- Packaged assets — safely bundle icons or other resources in your extension package
Architecture Overview
┌────────────────────────────────┐
│ Plotune Core │
│ - Manages extensions │
│ - Receives registration │
│ - Sends control/heartbeat │
└─────────────┬──────────────────┘
│
│ HTTP / WS
│
┌─────────────▼──────────────────┐
│ Plotune SDK │
│ ┌──────────────────────────┐ │
│ │ PlotuneServer (FastAPI) │ │
│ │ - /health, /read-file │ │
│ │ - @on_event, @on_ws │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ CoreClient (httpx) │ │
│ │ - register, heartbeat │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ PlotuneRuntime │ │
│ │ - lifecycle control │ │
│ │ - tray integration │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
Development Setup
# create virtual environment
python -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
# install dependencies
pip install plotune-sdk
User-Input Forms (Dynamic UI)
Extensions can ask the Plotune Core to show a modal form to the user.
The Core will:
- GET
/form→ receive the form schema (JSON). - Render the UI with
DynamicForm(PyQt5). - POST the collected data back to
/form.
You only have to implement two optional handlers:
| Endpoint | Method | Purpose | Required? |
|---|---|---|---|
/form |
GET |
Return the schema (dict) that describes tabs, groups and fields. |
Optional – if missing an empty form is shown. |
/form |
POST |
Receive the filled-in values (dict) and process them. |
Required – Core expects a response (default {"status":"success"}). |
1. Register the handlers (extension side)
# __main__.py (or any module that creates the server)
from plotune_sdk.server import PlotuneServer
from plotune_sdk.forms import FormLayout # optional helper
server = PlotuneServer(host="127.0.0.1", port=8100)
# ------------------------------------------------------------------
# GET /form → return the schema
# ------------------------------------------------------------------
@server.on_event("/form", "GET")
def get_form_schema(_):
# Build the schema with the fluent helper (recommended)
form = FormLayout()
form.add_tab("General") \
.add_text("username", "Username", default="", required=False) \
.add_number("seed", "Random seed", default=42, min_val=0, max_val=9999, required=True)
form.add_tab("Appearance") \
.add_combobox("theme", "Theme", options=["Light", "Dark", "Auto"], default="Auto") \
.add_checkbox("high_contrast", "High-contrast mode", default=False)
form.add_group("Advanced") \
.add_file("config_file", "Config file (optional)", required=False) \
.add_button(
"reset",
"Reset to defaults",
action={"method": "GET", "url": "/reset"} # optional custom action
)
return form.to_schema() # ← exact format expected by DynamicForm
# ------------------------------------------------------------------
# POST /form → process submitted data
# ------------------------------------------------------------------
@server.on_event("/form", "POST")
def handle_form_submission(data: dict):
# `data` contains the field keys exactly as you defined in the schema
username = data.get("username", "")
seed = data.get("seed")
theme = data.get("theme")
# … validate / store / start a task …
# Raise HTTPException for client-side errors
if not username:
raise HTTPException(status_code=400, detail="Username is required")
# Do something with the values
print(f"[Extension] User submitted: {username=}, {seed=}, {theme=}")
# Any dict you return is sent back to the Core (optional)
return {"status": "success", "saved": True}
2. Schema format (what DynamicForm expects)
{
"layout": [
{ "type": "tab", "label": "General", "fields": ["username","seed"] },
{ "type": "tab", "label": "Appearance","fields": ["theme","high_contrast"] },
{ "type": "group", "label": "Advanced", "fields": ["config_file","reset"] }
],
"fields": {
"username": { "type":"text", "label":"Username", "default":"", "required":false },
"seed": { "type":"number", "label":"Random seed", "default":"42", "min":0,"max":9999,"required":true },
"theme": { "type":"combobox", "label":"Theme", "options":["Light","Dark","Auto"], "default":"Auto","required":false },
"high_contrast": { "type":"checkbox", "label":"High-contrast mode","default":false,"required":false },
"config_file": { "type":"file", "label":"Config file (optional)","required":false },
"reset": { "type":"button", "label":"Reset to defaults",
"action":{"method":"GET","url":"/reset"} }
}
}
All fields are optional unless required: true.
3. plotune_sdk.forms – fluent schema builder
from plotune_sdk.forms import FormLayout
form = FormLayout()
form.add_tab("Settings") \
.add_text("api_key", "API Key", required=True) \
.add_number("timeout", "Timeout (s)", default=30, min_val=5, max_val=300)
schema = form.to_schema() # → dict ready for GET /form
| Method | Field type | Extra keys |
|---|---|---|
add_text |
text |
default, required |
add_number |
number |
default, min, max, required |
add_combobox |
combobox |
options, default, required |
add_checkbox |
checkbox |
default, required |
add_file |
file |
required |
add_button |
button |
action: {"method":…, "url":…, "payload_fields": […]} |
4. Testing locally
# 1. Run the extension
python -m my_extension
# 2. GET the schema
curl http://127.0.0.1:8100/form | jq .
# 3. POST a filled form
curl -X POST http://127.0.0.1:8100/form \
-H "Content-Type: application/json" \
-d '{"username":"alice","seed":123}'
The Core will automatically open the modal when the extension’s manifest contains
"ask_form": true.
That’s it!
Add the two @server.on_event decorators (or just one if you only need a static form) and you have a fully-featured, type-safe user-input dialog.
Example
# examples/example_extension.py
import time, random
from plotune_sdk.runtime import PlotuneRuntime
from plotune_sdk.server import PlotuneServer
server = PlotuneServer()
@server.on_event("/health", "GET")
def health(_):
return {"status": "active"}
@server.on_ws("fetch")
async def fetch_signal(signal_name, websocket, data):
print(f"Received WS signal {signal_name}: {data}")
await websocket.send_json({
"timestamp": time.time(),
"value": random.random()
})
runtime = PlotuneRuntime(
ext_name="file-extension",
host="127.0.0.1",
port=8010,
core_url="http://127.0.0.1:8000",
config={
"id": "file_extension",
"name": "File Extension",
"version": "1.0.0",
"mode": "offline",
"author": "Plotune SDK Team",
"enabled": True,
"connection": {"ip": "127.0.0.1", "port": 8010},
"configuration": {},
}
)
@runtime.tray("Open Logs")
def show_logs():
print("Opening log directory...")
if __name__ == "__main__":
runtime.start()
Extension Configuration Schema
All extensions must define a configuration payload that matches the ExtensionConfig model:
{
"name": "Simple Reader",
"id": "simple_reader",
"version": "1.0.0",
"description": "Reads table data from defined files.",
"mode": "offline",
"author": "Plotune Official",
"cmd": ["python", "__main__.py"],
"enabled": true,
"last_updated": "2025-06-15",
"git_path": "https://github.com/plotune/simple-reader",
"category": "Recorder",
"post_url": "http://localhost:8000/api/extension_click",
"file_formats": ["csv", "pltx"],
"ask_form": false,
"connection": {
"ip": "127.0.0.1",
"port": 8105,
"target": "127.0.0.1",
"target_port": 8000
},
"configuration": {
"file_path": {
"type": "string",
"description": "Path to the target file",
"default": ""
}
}
}
Packaging Assets
All icons or resources can be safely bundled inside your SDK package:
plotune_sdk/
├── assets/
│ └── icon.png
├── server.py
├── core.py
└── runtime.py
Access them using:
from importlib.resources import files
from PIL import Image
icon_path = files("plotune_sdk.assets") / "icon.png"
icon = Image.open(icon_path)
This works even when your extension is built as a .exe or packaged into a wheel.
License
Apache License 2.0 © 2025 — Plotune Team
For more details, visit https://plotune.net
Build. Extend. Integrate.
The Plotune SDK — your gateway to modular and intelligent extensions.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file plotune_sdk-0.1.0.tar.gz.
File metadata
- Download URL: plotune_sdk-0.1.0.tar.gz
- Upload date:
- Size: 30.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adf104e3342c442ad6d4ee5ed0e50760e4077af0adc55051ebd4deb010e93031
|
|
| MD5 |
013c06a418384a7119fc29da1569cd33
|
|
| BLAKE2b-256 |
4b5e1fcb6213fba5ba1861bfec236d0555066868e0a136a299f8fa1f24891679
|
File details
Details for the file plotune_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: plotune_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ba3455160cb2377955977286cd247b26a457c0c269dba01fc488085fae22d03
|
|
| MD5 |
1e0851e09e023f0cd4bc5812aec73f67
|
|
| BLAKE2b-256 |
bedf958e92416b300d8f251b896d8493b63b1d432eeca5374eabbee5e38903ed
|