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.0.tar.gz (30.0 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.0-py3-none-any.whl (28.9 kB view details)

Uploaded Python 3

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

Hashes for plotune_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 adf104e3342c442ad6d4ee5ed0e50760e4077af0adc55051ebd4deb010e93031
MD5 013c06a418384a7119fc29da1569cd33
BLAKE2b-256 4b5e1fcb6213fba5ba1861bfec236d0555066868e0a136a299f8fa1f24891679

See more details on using hashes here.

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

Hashes for plotune_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ba3455160cb2377955977286cd247b26a457c0c269dba01fc488085fae22d03
MD5 1e0851e09e023f0cd4bc5812aec73f67
BLAKE2b-256 bedf958e92416b300d8f251b896d8493b63b1d432eeca5374eabbee5e38903ed

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