Configuration & data storage

Config + State separation

config.yaml (edited by user):

  • Port numbers, enable flags, UI theme
  • Human-readable YAML format
  • Loaded when the plugin starts

DATA_DIR:

  • Counter states, window positions, user data
  • JSON format (structured)
  • Remains available across updates

Architecture

config/
└── config.yaml
    └── MyPlugin:
        ├── Enable: true
        ├── WebServerPort: 8001
        └── Theme: "dark"
                ↓
          [Plugin starts]
                ↓
data/
└── my_plugin_state.json
    └── {
          "counter": 42,
          "window_x": 100,
          "last_updated": 1234567890
        }

Load config (3 steps)

Step 1: Open YAML

from pathlib import Path
import yaml

CONFIG_FILE = ROOT_DIR / "config" / "config.yaml"

# With error handling
try:
    with open(CONFIG_FILE) as f:
        config = yaml.safe_load(f) or {}
except Exception as e:
    print(f"Config error: {e}")
    config = {}

Step 2: Read out values with defaults

# With .get() you stay safe from KeyErrors
port = config.get("MyPlugin", {}).get("WebServerPort", 8001)
enabled = config.get("MyPlugin", {}).get("Enable", True)
theme = config.get("MyPlugin", {}).get("Theme", "light")

Step 3: Use in the plugin

if enabled:
    app.run(port=port)
    set_theme(theme)

Practical example: saving data

import json
from pathlib import Path

# Paths
DATA_DIR = ROOT_DIR / "data"
STATE_FILE = DATA_DIR / "myplugin_state.json"

# Load data (or default)
def load_state():
    if STATE_FILE.exists():
        with open(STATE_FILE) as f:
            return json.load(f)
    return {"counter": 0, "wins": 0}

# Save data
def save_state(state):
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)

# Usage
state = load_state()
state["counter"] += 1
save_state(state)
import yaml
from pathlib import Path

CONFIG_FILE = (ROOT_DIR / "config" / "config.yaml").resolve()

try:
    with CONFIG_FILE.open("r", encoding="utf-8") as f:
        cfg = yaml.safe_load(f) or {}
except Exception as e:
    print(f"Config could not be loaded: {e}")
    cfg = {}

# Read out value with default value
port = cfg.get("MyPlugin", {}).get("WebServerPort", 8000)
enable = cfg.get("MyPlugin", {}).get("Enable", True)

With the .get() method you can safely query values without your program crashing if the key is missing.

Data storage: Where do I store my data?

There are several options, depending on your use case:

ROOT_DIR/
    data/
        window_state_timer.json
        window_state_deathcounter.json

Usage: Persistent data such as counter readings, window positions, user preferences.

from pathlib import Path

DATA_DIR = ROOT_DIR / "data"
STATE_FILE = DATA_DIR / "my_plugin_state.json"

# Save data
import json
state = {"counter": 42, "width": 800}
with STATE_FILE.open("w") as f:
    json.dump(state, f)

# Load data
if STATE_FILE.exists():
    with STATE_FILE.open("r") as f:
        state = json.load(f)
else:
    state = {"counter": 0, "width": 800}

Advantage: Data folder is retained during updates.

2. In the plugin folder itself (alternative)

src/plugins/my_plugin/
    main.py
    README.md
    version.txt
    plugin_data.json ← save directly here
PLUGIN_DIR = get_base_dir()
MY_DATA_FILE = PLUGIN_DIR / "plugin_data.json"
build/release/core/runtime/
    my_data.json

WARNING: The runtime folder is overwritten with every update! Use it only for temporary data that can be regenerated.

Practical example: Save window state

The Timer and DeathCounter save their window size/position:

# Load at startup
def load_win_size():
    if STATE_FILE.exists():
        try:
            with STATE_FILE.open("r") as f:
                return json.load(f)
        except:
            pass
    return {"width": 400, "height": 200}

# Save when window changes
@app.route("/save_dims", methods=["POST"])
def save_dims():
    with STATE_FILE.open("w") as f:
        json.dump(request.json, f)
    return "OK"

The front end calls /save_dims as soon as the user moves or enlarges the window.

YAML vs JSON

JSON: Faster to load, simple structure

import json
data = json.load(f)

YAML: Readable for humans, more complex structures

import yaml
data = yaml.safe_load(f)

Recommendation: Use JSON for plugin data (faster, fewer dependencies). YAML only for the main config.

Share data between plugins

Plugins can exchange files:

# Plugin A saves data
shared_data = {"wins": 10, "deaths": 3}
with (DATA_DIR / "shared_counter.json").open("w") as f:
    json.dump(shared_data, f)

# Plugin B reads data
with (DATA_DIR / "shared_counter.json").open("r") as f:
    data = json.load(f)
print(data["wins"])

But: Pay attention to race conditions! If both plugins write at the same time, data loss may occur. Use HTTP communication instead (see next chapter).


Summary

  • Load config: yaml.safe_load() from config.yaml
  • Save data: JSON in DATA_DIR for persistence
  • Fallback values: Always use .get() with defaults
  • Share: Via files (be careful of race conditions) or HTTP
  • Runtime folder: Only for temporary data, will be overwritten

Next chapter: GUI with pywebview