iints-sdk-python35 1.5.4__py3-none-any.whl → 1.5.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iints/__init__.py +7 -3
- iints/analysis/baseline.py +34 -0
- iints/api/registry.py +280 -0
- iints/cli/cli.py +2049 -96
- iints/core/algorithms/imitation_controller.py +78 -0
- iints/core/algorithms/neural_controller.py +89 -0
- iints/core/devices/__init__.py +2 -2
- iints/core/devices/models.py +178 -5
- iints/core/patient/bergman_model.py +28 -13
- iints/core/patient/profile.py +62 -1
- iints/core/physiology_variation.py +119 -0
- iints/core/simulator.py +43 -8
- iints/data/__init__.py +48 -1
- iints/data/datasets.json +62 -5
- iints/data/demo/demo_cgm.csv +288 -288
- iints/data/physiology_residual_profiles.json +13953 -0
- iints/data/realism_dashboard.py +390 -0
- iints/data/realism_reference.py +229 -0
- iints/data/realism_references.json +56 -0
- iints/data/realism_validator.py +813 -0
- iints/data/synthetic_mirror.py +183 -10
- iints/data/tidepool.py +239 -14
- iints/data/virtual_patients/reference_azt1d_t1d.yaml +12 -0
- iints/data/virtual_patients/reference_free_living_t1d.yaml +12 -0
- iints/data/virtual_patients/reference_hupa_ucm_t1d.yaml +12 -0
- iints/highlevel.py +38 -25
- iints/jetson/__init__.py +25 -0
- iints/jetson/endurance.py +1138 -0
- iints/jetson/research_pipeline.py +230 -0
- iints/live_patient/api.py +6 -0
- iints/live_patient/edge_ops.py +204 -0
- iints/live_patient/runtime.py +15 -14
- iints/presets/evidence_sources.yaml +9 -1
- iints/presets/presets.json +380 -18
- iints/research/__init__.py +59 -1
- iints/research/control.py +151 -0
- iints/research/control_eval.py +258 -0
- iints/research/data_blend.py +98 -0
- iints/research/evaluation.py +216 -1
- iints/research/neural_control.py +185 -0
- iints/scenarios/study_pack.py +31 -18
- iints/utils/run_io.py +11 -0
- iints/validation/run_validation.py +11 -0
- iints_sdk_python35-1.5.6.dist-info/METADATA +131 -0
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/RECORD +51 -33
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/entry_points.txt +1 -1
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/licenses/LICENSE +2 -2
- iints_sdk_python35-1.5.4.dist-info/METADATA +0 -200
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/licenses/LICENSE-MIT-IINTS-LEGACY +0 -0
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/licenses/NOTICE +0 -0
- {iints_sdk_python35-1.5.4.dist-info → iints_sdk_python35-1.5.6.dist-info}/top_level.txt +0 -0
iints/__init__.py
CHANGED
|
@@ -11,7 +11,7 @@ except ImportError: # pragma: no cover - Python < 3.8 fallback
|
|
|
11
11
|
try:
|
|
12
12
|
__version__ = version("iints-sdk-python35")
|
|
13
13
|
except PackageNotFoundError: # pragma: no cover - source tree fallback
|
|
14
|
-
__version__ = "1.5.
|
|
14
|
+
__version__ = "1.5.6"
|
|
15
15
|
|
|
16
16
|
# Note to developers: this SDK is currently maintained by a single author.
|
|
17
17
|
# Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
|
|
@@ -47,7 +47,7 @@ except Exception: # pragma: no cover - fallback if torch/device manager import
|
|
|
47
47
|
def get_device(self):
|
|
48
48
|
return self._device
|
|
49
49
|
from .core.safety import SafetyConfig, SafetySupervisor
|
|
50
|
-
from .core.devices.models import SensorModel, PumpModel
|
|
50
|
+
from .core.devices.models import SENSOR_PROFILES, SensorModel, PumpModel, create_sensor_model
|
|
51
51
|
from .core.algorithms.standard_pump_algo import StandardPumpAlgorithm
|
|
52
52
|
from .core.algorithms.mock_algorithms import (
|
|
53
53
|
ConstantDoseAlgorithm,
|
|
@@ -74,7 +74,7 @@ from .data.importer import (
|
|
|
74
74
|
summarize_carelink_csv,
|
|
75
75
|
)
|
|
76
76
|
from .data.nightscout import NightscoutConfig, import_nightscout
|
|
77
|
-
from .data.tidepool import TidepoolClient, load_openapi_spec
|
|
77
|
+
from .data.tidepool import TidepoolClient, TidepoolConfig, import_tidepool, load_openapi_spec
|
|
78
78
|
from .data.guardians import mdmp_gate, MDMPGateError
|
|
79
79
|
from .data.synthetic_mirror import generate_synthetic_mirror, SyntheticMirrorArtifact
|
|
80
80
|
from .data.study_corruption import AVAILABLE_STUDY_CORRUPTIONS, apply_study_corruptions, write_corrupted_study_csv
|
|
@@ -224,6 +224,8 @@ __all__ = [
|
|
|
224
224
|
"SafetyConfig",
|
|
225
225
|
"SensorModel",
|
|
226
226
|
"PumpModel",
|
|
227
|
+
"SENSOR_PROFILES",
|
|
228
|
+
"create_sensor_model",
|
|
227
229
|
"StandardPumpAlgorithm",
|
|
228
230
|
"ConstantDoseAlgorithm",
|
|
229
231
|
"RandomDoseAlgorithm",
|
|
@@ -247,6 +249,8 @@ __all__ = [
|
|
|
247
249
|
"NightscoutConfig",
|
|
248
250
|
"import_nightscout",
|
|
249
251
|
"TidepoolClient",
|
|
252
|
+
"TidepoolConfig",
|
|
253
|
+
"import_tidepool",
|
|
250
254
|
"load_openapi_spec",
|
|
251
255
|
"mdmp_gate",
|
|
252
256
|
"MDMPGateError",
|
iints/analysis/baseline.py
CHANGED
|
@@ -27,6 +27,32 @@ def compute_metrics(results_df: pd.DataFrame) -> Dict[str, float]:
|
|
|
27
27
|
return metrics.to_dict()
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _run_context(
|
|
31
|
+
*,
|
|
32
|
+
requested_duration_minutes: int,
|
|
33
|
+
safety_report: Dict[str, Any],
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
terminated_early = bool(safety_report.get("terminated_early", False))
|
|
36
|
+
termination = safety_report.get("termination_reason", {})
|
|
37
|
+
completed_duration_minutes = requested_duration_minutes
|
|
38
|
+
termination_reason = ""
|
|
39
|
+
if isinstance(termination, dict):
|
|
40
|
+
completed_duration_minutes = int(
|
|
41
|
+
termination.get("current_time_minutes", requested_duration_minutes)
|
|
42
|
+
)
|
|
43
|
+
termination_reason = str(termination.get("reason", ""))
|
|
44
|
+
return {
|
|
45
|
+
"requested_duration_minutes": int(requested_duration_minutes),
|
|
46
|
+
"completed_duration_minutes": int(completed_duration_minutes),
|
|
47
|
+
"completion_ratio_pct": round(
|
|
48
|
+
(completed_duration_minutes / max(requested_duration_minutes, 1)) * 100.0,
|
|
49
|
+
2,
|
|
50
|
+
),
|
|
51
|
+
"terminated_early": terminated_early,
|
|
52
|
+
"termination_reason": termination_reason,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
30
56
|
def run_baseline_comparison(
|
|
31
57
|
patient_params: Dict[str, Any],
|
|
32
58
|
stress_event_payloads: List[Dict[str, Any]],
|
|
@@ -44,6 +70,10 @@ def run_baseline_comparison(
|
|
|
44
70
|
rows.append(
|
|
45
71
|
{
|
|
46
72
|
"algorithm": primary_label,
|
|
73
|
+
**_run_context(
|
|
74
|
+
requested_duration_minutes=duration,
|
|
75
|
+
safety_report=primary_safety,
|
|
76
|
+
),
|
|
47
77
|
"tir_70_180": primary_metrics.get("tir_70_180", 0.0),
|
|
48
78
|
"tir_below_70": primary_metrics.get("tir_below_70", 0.0),
|
|
49
79
|
"tir_above_180": primary_metrics.get("tir_above_180", 0.0),
|
|
@@ -74,6 +104,10 @@ def run_baseline_comparison(
|
|
|
74
104
|
rows.append(
|
|
75
105
|
{
|
|
76
106
|
"algorithm": label,
|
|
107
|
+
**_run_context(
|
|
108
|
+
requested_duration_minutes=duration,
|
|
109
|
+
safety_report=safety_report,
|
|
110
|
+
),
|
|
77
111
|
"tir_70_180": metrics.get("tir_70_180", 0.0),
|
|
78
112
|
"tir_below_70": metrics.get("tir_below_70", 0.0),
|
|
79
113
|
"tir_above_180": metrics.get("tir_above_180", 0.0),
|
iints/api/registry.py
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
4
13
|
from typing import List, Optional, Mapping, Sequence, Iterable, Any, cast
|
|
5
14
|
import importlib
|
|
15
|
+
import importlib.util
|
|
6
16
|
|
|
7
17
|
try:
|
|
8
18
|
from importlib import metadata as importlib_metadata
|
|
@@ -12,6 +22,10 @@ except Exception: # pragma: no cover
|
|
|
12
22
|
from iints.api.base_algorithm import InsulinAlgorithm, AlgorithmMetadata
|
|
13
23
|
from iints.core.algorithms.discovery import discover_algorithms
|
|
14
24
|
|
|
25
|
+
PLUGIN_REGISTRY_SCHEMA_VERSION = "1.0"
|
|
26
|
+
IINTS_PLUGIN_HOME_ENV = "IINTS_PLUGIN_HOME"
|
|
27
|
+
SUPPORTED_LOCAL_PLUGIN_KINDS = {"algorithm", "patient_model", "data_source", "validator"}
|
|
28
|
+
|
|
15
29
|
|
|
16
30
|
@dataclass
|
|
17
31
|
class AlgorithmListing:
|
|
@@ -21,6 +35,231 @@ class AlgorithmListing:
|
|
|
21
35
|
metadata: Optional[AlgorithmMetadata]
|
|
22
36
|
status: str = "available"
|
|
23
37
|
error: Optional[str] = None
|
|
38
|
+
plugin_path: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class LocalPluginRecord:
|
|
43
|
+
kind: str
|
|
44
|
+
name: str
|
|
45
|
+
installed_path: str
|
|
46
|
+
source_path: str
|
|
47
|
+
module_stem: str
|
|
48
|
+
class_name: Optional[str] = None
|
|
49
|
+
registered_at_utc: str = ""
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_dict(cls, payload: Mapping[str, Any]) -> "LocalPluginRecord":
|
|
53
|
+
return cls(
|
|
54
|
+
kind=str(payload.get("kind", "")),
|
|
55
|
+
name=str(payload.get("name", "")),
|
|
56
|
+
installed_path=str(payload.get("installed_path", "")),
|
|
57
|
+
source_path=str(payload.get("source_path", "")),
|
|
58
|
+
module_stem=str(payload.get("module_stem", "")),
|
|
59
|
+
class_name=str(payload["class_name"]) if payload.get("class_name") else None,
|
|
60
|
+
registered_at_utc=str(payload.get("registered_at_utc", "")),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def to_dict(self) -> dict[str, Any]:
|
|
64
|
+
return {
|
|
65
|
+
"kind": self.kind,
|
|
66
|
+
"name": self.name,
|
|
67
|
+
"installed_path": self.installed_path,
|
|
68
|
+
"source_path": self.source_path,
|
|
69
|
+
"module_stem": self.module_stem,
|
|
70
|
+
"class_name": self.class_name,
|
|
71
|
+
"registered_at_utc": self.registered_at_utc,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_plugin_home() -> Path:
|
|
76
|
+
"""Return the writable local plugin home used by CLI installs."""
|
|
77
|
+
override = os.getenv(IINTS_PLUGIN_HOME_ENV)
|
|
78
|
+
if override:
|
|
79
|
+
return Path(override).expanduser().resolve()
|
|
80
|
+
return (Path.home() / ".iints" / "plugins").resolve()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_plugin_registry_path() -> Path:
|
|
84
|
+
return get_plugin_home() / "registry.json"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _empty_registry() -> dict[str, Any]:
|
|
88
|
+
return {"schema_version": PLUGIN_REGISTRY_SCHEMA_VERSION, "plugins": []}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _read_local_plugin_registry() -> dict[str, Any]:
|
|
92
|
+
path = get_plugin_registry_path()
|
|
93
|
+
if not path.exists():
|
|
94
|
+
return _empty_registry()
|
|
95
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
96
|
+
if not isinstance(payload, dict):
|
|
97
|
+
raise ValueError(f"Plugin registry must be a JSON object: {path}")
|
|
98
|
+
plugins = payload.get("plugins", [])
|
|
99
|
+
if not isinstance(plugins, list):
|
|
100
|
+
raise ValueError(f"Plugin registry 'plugins' must be a list: {path}")
|
|
101
|
+
return {
|
|
102
|
+
"schema_version": str(payload.get("schema_version") or PLUGIN_REGISTRY_SCHEMA_VERSION),
|
|
103
|
+
"plugins": plugins,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _write_local_plugin_registry(payload: Mapping[str, Any]) -> Path:
|
|
108
|
+
path = get_plugin_registry_path()
|
|
109
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
safe_payload = {
|
|
111
|
+
"schema_version": PLUGIN_REGISTRY_SCHEMA_VERSION,
|
|
112
|
+
"plugins": list(payload.get("plugins", [])),
|
|
113
|
+
}
|
|
114
|
+
path.write_text(json.dumps(safe_payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
115
|
+
return path
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _slugify_plugin_name(name: str) -> str:
|
|
119
|
+
slug = re.sub(r"[^a-zA-Z0-9._-]+", "-", name.strip().lower()).strip("-._")
|
|
120
|
+
return slug or "plugin"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _kind_directory(kind: str) -> str:
|
|
124
|
+
return {
|
|
125
|
+
"algorithm": "algorithms",
|
|
126
|
+
"patient_model": "patient_models",
|
|
127
|
+
"data_source": "data_sources",
|
|
128
|
+
"validator": "validators",
|
|
129
|
+
}[kind]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _module_name_for_path(path: Path) -> str:
|
|
133
|
+
digest = hashlib.sha256(str(path).encode("utf-8")).hexdigest()[:12]
|
|
134
|
+
return f"_iints_local_plugin_{path.stem}_{digest}_{time.monotonic_ns()}"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _load_algorithm_class_from_path(path: Path) -> type[InsulinAlgorithm]:
|
|
138
|
+
resolved = path.expanduser().resolve()
|
|
139
|
+
if not resolved.is_file():
|
|
140
|
+
raise FileNotFoundError(f"Plugin file not found: {resolved}")
|
|
141
|
+
module_name = _module_name_for_path(resolved)
|
|
142
|
+
spec = importlib.util.spec_from_file_location(module_name, resolved)
|
|
143
|
+
if spec is None or spec.loader is None:
|
|
144
|
+
raise ImportError(f"Could not load plugin module spec: {resolved}")
|
|
145
|
+
module = importlib.util.module_from_spec(spec)
|
|
146
|
+
module.__dict__.setdefault("iints", importlib.import_module("iints"))
|
|
147
|
+
previous_module = sys.modules.get(module_name)
|
|
148
|
+
sys.modules[module_name] = module
|
|
149
|
+
try:
|
|
150
|
+
spec.loader.exec_module(module)
|
|
151
|
+
finally:
|
|
152
|
+
if previous_module is None:
|
|
153
|
+
sys.modules.pop(module_name, None)
|
|
154
|
+
else:
|
|
155
|
+
sys.modules[module_name] = previous_module
|
|
156
|
+
for _, obj in module.__dict__.items():
|
|
157
|
+
if isinstance(obj, type) and issubclass(obj, InsulinAlgorithm) and obj is not InsulinAlgorithm:
|
|
158
|
+
return obj
|
|
159
|
+
raise ImportError(f"No InsulinAlgorithm subclass found in plugin file: {resolved}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _store_local_plugin_record(record: LocalPluginRecord) -> Path:
|
|
163
|
+
payload = _read_local_plugin_registry()
|
|
164
|
+
records = [
|
|
165
|
+
item
|
|
166
|
+
for item in payload.get("plugins", [])
|
|
167
|
+
if not (
|
|
168
|
+
isinstance(item, dict)
|
|
169
|
+
and str(item.get("kind")) == record.kind
|
|
170
|
+
and str(item.get("name")).lower() == record.name.lower()
|
|
171
|
+
)
|
|
172
|
+
]
|
|
173
|
+
records.append(record.to_dict())
|
|
174
|
+
payload["plugins"] = records
|
|
175
|
+
return _write_local_plugin_registry(payload)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def list_local_plugin_records(kind: str | None = None) -> list[LocalPluginRecord]:
|
|
179
|
+
payload = _read_local_plugin_registry()
|
|
180
|
+
records: list[LocalPluginRecord] = []
|
|
181
|
+
for item in payload.get("plugins", []):
|
|
182
|
+
if not isinstance(item, dict):
|
|
183
|
+
continue
|
|
184
|
+
record = LocalPluginRecord.from_dict(item)
|
|
185
|
+
if kind is None or record.kind == kind:
|
|
186
|
+
records.append(record)
|
|
187
|
+
return sorted(records, key=lambda record: (record.kind, record.name.lower()))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def install_file_plugin(kind: str, source_path: str | Path, name: str | None = None) -> LocalPluginRecord:
|
|
191
|
+
"""Install a local extension file into the user plugin home."""
|
|
192
|
+
normalized_kind = kind.strip().lower().replace("-", "_")
|
|
193
|
+
if normalized_kind not in SUPPORTED_LOCAL_PLUGIN_KINDS:
|
|
194
|
+
supported = ", ".join(sorted(SUPPORTED_LOCAL_PLUGIN_KINDS))
|
|
195
|
+
raise ValueError(f"Unsupported plugin kind '{kind}'. Supported kinds: {supported}")
|
|
196
|
+
|
|
197
|
+
source = Path(source_path).expanduser().resolve()
|
|
198
|
+
if not source.is_file():
|
|
199
|
+
raise FileNotFoundError(f"Plugin file not found: {source}")
|
|
200
|
+
if source.suffix != ".py":
|
|
201
|
+
raise ValueError("Local plugins must be Python .py files.")
|
|
202
|
+
|
|
203
|
+
display_name = (name or source.stem).strip()
|
|
204
|
+
if not display_name:
|
|
205
|
+
raise ValueError("Plugin name cannot be empty.")
|
|
206
|
+
slug = _slugify_plugin_name(display_name)
|
|
207
|
+
target_dir = get_plugin_home() / _kind_directory(normalized_kind)
|
|
208
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
209
|
+
target = target_dir / f"{slug}.py"
|
|
210
|
+
if source != target:
|
|
211
|
+
shutil.copy2(source, target)
|
|
212
|
+
|
|
213
|
+
record = LocalPluginRecord(
|
|
214
|
+
kind=normalized_kind,
|
|
215
|
+
name=display_name,
|
|
216
|
+
installed_path=str(target),
|
|
217
|
+
source_path=str(source),
|
|
218
|
+
module_stem=target.stem,
|
|
219
|
+
class_name=None,
|
|
220
|
+
registered_at_utc=datetime.now(timezone.utc).isoformat(),
|
|
221
|
+
)
|
|
222
|
+
_store_local_plugin_record(record)
|
|
223
|
+
return record
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def install_algorithm_plugin(source_path: str | Path, name: str | None = None) -> LocalPluginRecord:
|
|
227
|
+
"""Validate and install a local InsulinAlgorithm plugin file."""
|
|
228
|
+
source = Path(source_path).expanduser().resolve()
|
|
229
|
+
algorithm_class = _load_algorithm_class_from_path(source)
|
|
230
|
+
instance = algorithm_class()
|
|
231
|
+
metadata = instance.get_algorithm_metadata()
|
|
232
|
+
display_name = (name or metadata.name or source.stem).strip()
|
|
233
|
+
record = install_file_plugin("algorithm", source, display_name)
|
|
234
|
+
record.class_name = algorithm_class.__name__
|
|
235
|
+
_store_local_plugin_record(record)
|
|
236
|
+
return record
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def uninstall_local_plugin(name: str, kind: str | None = None, *, remove_file: bool = False) -> bool:
|
|
240
|
+
payload = _read_local_plugin_registry()
|
|
241
|
+
normalized_kind = kind.strip().lower().replace("-", "_") if kind else None
|
|
242
|
+
kept: list[dict[str, Any]] = []
|
|
243
|
+
removed = False
|
|
244
|
+
for item in payload.get("plugins", []):
|
|
245
|
+
if not isinstance(item, dict):
|
|
246
|
+
continue
|
|
247
|
+
record = LocalPluginRecord.from_dict(item)
|
|
248
|
+
matches_name = record.name.lower() == name.lower()
|
|
249
|
+
matches_kind = normalized_kind is None or record.kind == normalized_kind
|
|
250
|
+
if matches_name and matches_kind:
|
|
251
|
+
removed = True
|
|
252
|
+
if remove_file:
|
|
253
|
+
try:
|
|
254
|
+
Path(record.installed_path).unlink(missing_ok=True)
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
continue
|
|
258
|
+
kept.append(record.to_dict())
|
|
259
|
+
if removed:
|
|
260
|
+
payload["plugins"] = kept
|
|
261
|
+
_write_local_plugin_registry(payload)
|
|
262
|
+
return removed
|
|
24
263
|
|
|
25
264
|
|
|
26
265
|
def _load_entry_point(ep) -> AlgorithmListing:
|
|
@@ -100,4 +339,45 @@ def list_algorithm_plugins() -> List[AlgorithmListing]:
|
|
|
100
339
|
except Exception:
|
|
101
340
|
pass
|
|
102
341
|
|
|
342
|
+
# Local user-installed plugins
|
|
343
|
+
try:
|
|
344
|
+
for record in list_local_plugin_records("algorithm"):
|
|
345
|
+
plugin_path = Path(record.installed_path)
|
|
346
|
+
try:
|
|
347
|
+
algorithm_class = _load_algorithm_class_from_path(plugin_path)
|
|
348
|
+
instance = algorithm_class()
|
|
349
|
+
meta = instance.get_algorithm_metadata()
|
|
350
|
+
listings.append(
|
|
351
|
+
AlgorithmListing(
|
|
352
|
+
name=record.name or meta.name,
|
|
353
|
+
class_path=f"{plugin_path}:{algorithm_class.__name__}",
|
|
354
|
+
source="local",
|
|
355
|
+
metadata=meta,
|
|
356
|
+
plugin_path=str(plugin_path),
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
except Exception as exc:
|
|
360
|
+
listings.append(
|
|
361
|
+
AlgorithmListing(
|
|
362
|
+
name=record.name,
|
|
363
|
+
class_path=f"{plugin_path}:{record.class_name or ''}",
|
|
364
|
+
source="local",
|
|
365
|
+
metadata=None,
|
|
366
|
+
status="unavailable",
|
|
367
|
+
error=str(exc),
|
|
368
|
+
plugin_path=str(plugin_path),
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
except Exception as exc:
|
|
372
|
+
listings.append(
|
|
373
|
+
AlgorithmListing(
|
|
374
|
+
name="local plugin registry",
|
|
375
|
+
class_path=str(get_plugin_registry_path()),
|
|
376
|
+
source="local",
|
|
377
|
+
metadata=None,
|
|
378
|
+
status="unavailable",
|
|
379
|
+
error=str(exc),
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
|
|
103
383
|
return listings
|