"""Probe registry — discovers and runs probes by pattern matching.
Supports two discovery mechanisms:
1. Built-in: auto-discover probes under probes/ package
2. Plugins: external packages register via entry_points group 'metapod.probes'
To create a plugin:
# In your package's pyproject.toml:
[project.entry-points."metapod.probes"]
my_source = "my_package.probes"
"""
from __future__ import annotations
import fnmatch
import importlib
import pkgutil
from pathlib import Path
from core.base_probe import BaseProbe
# Global registry
_PROBES: dict[str, type[BaseProbe]] = {}
_discovered = False
[docs]
def register(cls: type[BaseProbe]) -> type[BaseProbe]:
"""Decorator to register a probe class."""
name = cls.probe_name()
if name in _PROBES:
import logging
logging.getLogger("metapod").warning("Duplicate probe registration: %s (overwriting)", name)
_PROBES[name] = cls
return cls
[docs]
def get_probe(name: str) -> type[BaseProbe] | None:
return _PROBES.get(name)
[docs]
def list_probes(pattern: str = "*") -> list[str]:
"""List registered probe names matching a glob pattern."""
return sorted(name for name in _PROBES if fnmatch.fnmatch(name, pattern))
[docs]
def get_probes(pattern: str = "*") -> list[type[BaseProbe]]:
"""Get probe classes matching a glob pattern."""
return [_PROBES[name] for name in list_probes(pattern)]
[docs]
def discover_probes():
"""Auto-discover all probes: built-in + plugins."""
global _discovered
if _discovered:
return
_discovered = True
# 1. Built-in probes under probes/ package
probes_dir = Path(__file__).parent.parent / "probes"
if probes_dir.exists():
for source_type in probes_dir.iterdir():
if not source_type.is_dir() or source_type.name.startswith("_"):
continue
for category in source_type.iterdir():
if not category.is_dir() or category.name.startswith("_"):
continue
for module_info in pkgutil.iter_modules([str(category)]):
if module_info.name.startswith("_"):
continue
module_path = f"probes.{source_type.name}.{category.name}.{module_info.name}"
try:
importlib.import_module(module_path)
except Exception as e:
import logging
logging.getLogger("metapod").debug("Failed to load %s: %s", module_path, e)
# 2. External plugins via entry_points
try:
if hasattr(importlib.metadata, "entry_points"):
eps = importlib.metadata.entry_points()
# Python 3.12+ returns a dict, 3.10-3.11 returns SelectableGroups
if hasattr(eps, "select"):
plugin_eps = eps.select(group="metapod.probes")
elif isinstance(eps, dict):
plugin_eps = eps.get("metapod.probes", [])
else:
plugin_eps = getattr(eps, "metapod.probes", [])
for ep in plugin_eps:
try:
ep.load()
except Exception as e:
import logging
logging.getLogger("metapod").debug("Plugin load failed %s: %s", ep.name, e)
except Exception:
pass