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

Uploaded Python 3

File details

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

File metadata

  • Download URL: plotune_sdk-0.1.0a3.tar.gz
  • Upload date:
  • Size: 27.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.0a3.tar.gz
Algorithm Hash digest
SHA256 094c02f7eb410236c9f2c4798b686a3863a74bfc25496f9e0868c24fe5c15ceb
MD5 825293daeb3777bcadf2fc19cc6e93cb
BLAKE2b-256 5a641acdbbe843be50d7f8d2eca589ff23081bb30a69ceba594789f6780753c8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: plotune_sdk-0.1.0a3-py3-none-any.whl
  • Upload date:
  • Size: 24.1 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.0a3-py3-none-any.whl
Algorithm Hash digest
SHA256 e174bfbc04f4f13ca69869062a99cb9e366182dde30729d39692655b4aeea14b
MD5 03f5b42e339dbdacee4fab368514adef
BLAKE2b-256 34eecd8784dfc0a4330f08efdf73dec0dcf7d32bc5ebde34e8f90e9055f93f4b

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