diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-08-02 01:34:43 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-08-02 01:34:43 -0600 |
| commit | a3aa1df99df8f413cac2ba6020b7cd0dec6d2390 (patch) | |
| tree | a4c5feca6b0db326d9e54f417abc132c1270a7a3 /plugins | |
| parent | f5066a8ca2fb50aa3dddf2c8847e52574cdde6ad (diff) | |
| parent | f1aaffbc3eb1e2c154315c556d2555803eea7997 (diff) | |
Merge origin/main: reconcile ConfigWindow consolidation with Debug window + protocol updates
origin/main (23 commits) added a Debug window/log system on top of the old
separate Devices/Channels/Plot windows, plus protocol fixes (cml, mark10,
modbus_rtu, scpi) and device/profile changes. Local main (3 commits)
replaced the separate windows with a unified ConfigWindow + dock panel.
Kept local's ConfigWindow/dock architecture and ported the Debug window
onto it: new toolbar button + _open_debug(), gated by developer_mode same
as origin's version. Deduped the two independent "developer mode" toggles
that had collided in SettingsWindow (origin's General-tab checkbox gating
Debug window + device sim-mode visibility, local's Advanced-tab checkbox
setting verbose logging) into one General-tab checkbox that does both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'plugins')
| -rw-r--r-- | plugins/motion_capture/device.py | 1 | ||||
| -rw-r--r-- | plugins/plugin_manager.py | 40 |
2 files changed, 40 insertions, 1 deletions
diff --git a/plugins/motion_capture/device.py b/plugins/motion_capture/device.py index 58f89ce..3369962 100644 --- a/plugins/motion_capture/device.py +++ b/plugins/motion_capture/device.py @@ -102,6 +102,7 @@ class CameraDevice(BaseDevice): return { "device_type": self.DEVICE_TYPE, "device_id": self.info.device_id, + "name": self.info.name, "camera_index": self._camera_index, "simulate": self._simulate, "resolution": list(self._resolution) if self._resolution else None, diff --git a/plugins/plugin_manager.py b/plugins/plugin_manager.py index 36ae71a..5cefc2e 100644 --- a/plugins/plugin_manager.py +++ b/plugins/plugin_manager.py @@ -10,12 +10,14 @@ per-plugin state separately via get_save_state / apply_save_state. from __future__ import annotations +import importlib.metadata import importlib.util import json import os +import re import sys import traceback -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Optional from plugins.base_plugin import LabPlugin, PluginContext @@ -35,9 +37,35 @@ class PluginManifest: description: str = "" author: str = "" entry_point: str = "plugin.Plugin" # "module.ClassName" relative to plugin dir + requires: List[str] = field(default_factory=list) # pip-style reqs, e.g. "opencv-python>=4.8.0" plugin_dir: str = "" +def _dist_name(requirement: str) -> str: + """Extract the distribution name from a requirement string, e.g. + "opencv-python>=4.8.0" -> "opencv-python".""" + return re.split(r"[<>=!~\[; ]", requirement.strip(), maxsplit=1)[0] + + +def missing_requirements(requires: List[str]) -> List[str]: + """Return the subset of `requires` whose distribution isn't installed. + + Checked by distribution name via importlib.metadata (matches what pip + installed it as), not by import name — those differ for packages like + opencv-python (imports as cv2) or pyserial (imports as serial). + """ + missing = [] + for req in requires: + name = _dist_name(req) + if not name: + continue + try: + importlib.metadata.version(name) + except importlib.metadata.PackageNotFoundError: + missing.append(req) + return missing + + # ── Manager ─────────────────────────────────────────────────────────────────── class PluginManager: @@ -94,6 +122,7 @@ class PluginManager: description = data.get("description", ""), author = data.get("author", ""), entry_point = data.get("entry_point", "plugin.Plugin"), + requires = data.get("requires", []), plugin_dir = plugin_dir, ) self._manifests[m.plugin_id] = m @@ -146,6 +175,11 @@ class PluginManager: print(f"[PluginManager] No manifest for '{plugin_id}'") return None + missing = missing_requirements(manifest.requires) + if missing: + print(f"[Plugin] '{plugin_id}' missing dependencies: {', '.join(missing)}") + return None + module_name, class_name = manifest.entry_point.rsplit(".", 1) module_file = os.path.join( manifest.plugin_dir, *module_name.split("/") @@ -218,6 +252,10 @@ class PluginManager: def get_manifests(self) -> List[PluginManifest]: return list(self._manifests.values()) + def get_missing_dependencies(self, plugin_id: str) -> List[str]: + manifest = self._manifests.get(plugin_id) + return missing_requirements(manifest.requires) if manifest else [] + def get_loaded(self) -> List[LabPlugin]: return list(self._loaded.values()) |
