Skip to main content

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:

  1. GET /form → receive the form schema (JSON).
  2. Render the UI with DynamicForm (PyQt5).
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plotune_sdk-0.1.0b0.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plotune_sdk-0.1.0b0-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file plotune_sdk-0.1.0b0.tar.gz.

File metadata

  • Download URL: plotune_sdk-0.1.0b0.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for plotune_sdk-0.1.0b0.tar.gz
Algorithm Hash digest
SHA256 5322ef1e331b4d77e5e77aac012e4b97d973154804e0ad9ee9ec2333a8d88b7c
MD5 cc4fa525d5dad9342136e0863e882fa4
BLAKE2b-256 47163eb36b6050d75e9e03f2c06391b40570ac1750c8ddd873b8b283ef6f666c

See more details on using hashes here.

File details

Details for the file plotune_sdk-0.1.0b0-py3-none-any.whl.

File metadata

  • Download URL: plotune_sdk-0.1.0b0-py3-none-any.whl
  • Upload date:
  • Size: 23.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for plotune_sdk-0.1.0b0-py3-none-any.whl
Algorithm Hash digest
SHA256 ed9ed6925bf857cd51154aabd61abf1d29fd23048e651b85559153e52b688d77
MD5 bb2674888d9ba42af016528a4b83390a
BLAKE2b-256 09e3e1a618bbb69d4513d21ce2b185d2bf16618718f568f3adc8d81c23032c9d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page