I’m embedding a small UI with pywebview and want Python to JS live updates. I created a GPSSpoofingDetector class that loads a pickled sklearn model and a pandas test CSV. I want a JavaScript “Start” button to call a Python method that runs random_sample_testing, and during that test the Python code calls window.evaluate_js(...) to push JSON updates to the page.
This works if I run the test as the webview.start() callback (i.e. webview.start(detector.random_sample_testing, args=(window, data, feature_names), http_server=True)), but when I expose a Python API object to JS using js_api=api in webview.create_window() I immediately get errors before any button is clicked.
GPStest.py:
import time
import json
import webview
import pickle
import pandas as pd
import numpy as np
import warnings
from pathlib import Path
warnings.filterwarnings('ignore')
class GPSSpoofingDetector:
"""Wrap original script into a class with small functions while preserving
exact prints, returns and behavior.
Usage: run this file directly. It will perform the same prints and return
values as the original script.
"""
def __init__(self):
# constants kept exactly as in the original script
self.PROJECT_ROOT = Path(__file__).resolve().parents[2] # -> GPS_Spoofing_Detection
self.MODEL_PATH = self.PROJECT_ROOT / "model" / "DT_model.pkl"
self.TEST_CSV = self.PROJECT_ROOT / "dataset" / "testing" / "test_data.csv"
self.FEATURE_COLUMNS = [2, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 26]
self.clf = None
self.scaler = None
print('initializing')
def load_model(self):
"""Load the trained model and scaler from pickle. Prints same messages."""
print("Loading model...")
with open(self.MODEL_PATH, 'rb') as f:
self.clf, self.scaler = pickle.load(f)
print("✓ Model loaded successfully\n")
def predict_spoofing(self, gps_features_df, show_details=True):
"""
Predict GPS spoofing from features
Parameters:
- gps_features_df: DataFrame with proper column names
- show_details: show prediction probabilities
Returns: prediction label and numeric class
"""
features_scaled = self.scaler.transform(gps_features_df)
prediction = self.clf.predict(features_scaled)
labels = {0: 'Clean', 1: 'Static Spoofing', 2: 'Dynamic Spoofing'}
result = labels[prediction[0]]
if show_details and hasattr(self.clf, 'predict_proba'):
probas = self.clf.predict_proba(features_scaled)[0]
print(f"Prediction: {result}")
print(f"Confidence:")
for i, label in labels.items():
print(f" {label}: {probas[i]*100:.2f}%")
return result, prediction[0]
def load_data(self):
"""Load test CSV and return DataFrame. Keeps same variable names."""
data = pd.read_csv(self.TEST_CSV)
return data
def print_features_used(self, data):
"""Print feature names used exactly as original script."""
feature_names = data.columns[self.FEATURE_COLUMNS].tolist()
print(f"\nFeatures used: {feature_names}\n")
return feature_names
def random_sample_testing(self, window, data, feature_names):
"""Perform the same random testing section and prints identical output."""
print("="*60)
print("RANDOM SAMPLE TESTING (10 samples)")
print("="*60)
np.random.seed(42)
random_indices = np.random.choice(len(data), 20, replace=False)
correct = 0
labels = {0: 'Clean', 1: 'Static Spoofing', 2: 'Dynamic Spoofing'}
for i, idx in enumerate(random_indices, 1):
sample = data.iloc[[idx]][feature_names]
actual = int(data.iloc[idx, 0])
prediction, pred_num = self.predict_spoofing(sample, show_details=False)
match = "✓" if pred_num == actual else "✗"
if pred_num == actual:
correct += 1
if window:
extra_cols = ['Label', 'lat', 'lon', 'alt', 'satellites_used'] # customize this list
available = [c for c in extra_cols if c in data.columns]
meta = data.loc[idx, available].to_dict() if available else {}
print('meta: ', meta)
# convert to JSON string so JS can parse it safely
js_cmd = f'addData({json.dumps(meta)})'
# Push to the webview. Use the window object returned by create_window.
# This runs from a background thread and is allowed.
window.evaluate_js(js_cmd)
# simulate work
time.sleep(1)
print(f"{i:2d}. Actual: {labels[actual]:20s} | Predicted: {prediction:20s} {match}")
if window:
# notify JS that we're done
window.evaluate_js("addData('Done producing data.')")
print(f"\nAccuracy: {correct}/10 = {correct*10}%")
app.py:
from pathlib import Path
import time
import json
from functools import partial
import threading
import webview
from GPStest import GPSSpoofingDetector
class Api:
def __init__(self, detector, data, feature_names, window:None|webview.Window):
self.detector = detector
self.data = data
self.feature_names = feature_names
self.window = window
def run_test(self):
# This will be called from JS
# Start a thread so UI doesn’t freeze
import threading
t = threading.Thread(target=detector.random_sample_testing, args=(window, data, feature_names))
t.start()
return "Started"
if __name__ == '__main__':
detector = GPSSpoofingDetector()
detector.load_model()
data = detector.load_data()
feature_names = detector.print_features_used(data)
api = Api(detector, data, feature_names, None)
window = webview.create_window('Live updates demo', url="app2.html", js_api=api)
api.window = window
print('starting webview')
webview.start(http_server=True)
app2.html:
<head>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('pybutn').addEventListener('click', function() {
if (window.pywebview && window.pywebview.api) {
window.pywebview.api.run_test();
} else {
console.warn('pywebview API not ready yet');
setTimeout(()=> window.pywebview?.api?.run_test(), 300);
}
});
// called by Python via window.evaluate_js("addData(...);")
window.addData = function(obj) {
const out = document.getElementById('out');
const d = document.createElement('div');
d.className = 'item';
if (typeof obj === 'object' && obj !== null) {
// You can access individual fields:
d.innerHTML = `
<b>Label:</b> ${obj.Label ?? 'N/A'}<br>
<b>Lat:</b> ${obj.lat ?? 'N/A'}<br>
<b>Lon:</b> ${obj.lon ?? 'N/A'}<br>
<b>Alt:</b> ${obj.alt ?? 'N/A'}<br>
<b>Sats:</b> ${obj.satellites_used ?? 'N/A'}
`;
} else {
// obj might already be an object if evaluate_js passes it, but we send JSON string
d.textContent = typeof obj === 'string' ? obj : JSON.stringify(obj);
}
out.appendChild(d);
out.scrollTop = out.scrollHeight; // auto-scroll
}
});
</script>
</head>
error:
initializing
Loading model...
✓ Model loaded successfully
Features used: ['time_utc_usec', 's_variance_m_s', 'c_variance_rad', 'epv', 'hdop', 'vdop', 'noise_per_ms', 'jamming_indicator', 'vel_m_s', 'vel_n_m_s', 'vel_e_m_s', 'vel_d_m_s', 'cog_rad', 'satellites_used']
starting webview
[pywebview] Error while processing data.Label.array.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T ...
What I expect:
GPSSpoofingDetectorloads model & CSV at script start (console prints show that).The web UI starts and waits for me to click "Start".
When clicking "Start", JS calls
window.pywebview.api.run_test().run_test()starts a background thread that runsdetector.random_sample_testing(...). That method pushes JSON to the page viawindow.evaluate_js(...)and I see live updates in the page.
What actually happens:
The console output shows the model is loaded and features printed, then starting webview, but immediately I get long pywebview errors before I press Start