huge refactor of plugin system
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+99
-3
@@ -25,6 +25,7 @@ from Store._base import Store as BaseStore
|
||||
_SHA256_HEX_RE = re.compile(r"^[0-9a-fA-F]{64}$")
|
||||
|
||||
_DISCOVERED_CLASSES_CACHE: Optional[Dict[str, Type[BaseStore]]] = None
|
||||
_PLUGIN_DISCOVERED_CLASSES_CACHE: Dict[str, Optional[Type[BaseStore]]] = {}
|
||||
|
||||
# Backends that failed to initialize earlier in the current process.
|
||||
# Keyed by (store_type, instance_key) where instance_key is the name used under config.store.<type>.<instance_key>.
|
||||
@@ -85,6 +86,101 @@ def _discover_store_classes() -> Dict[str, Type[BaseStore]]:
|
||||
return discovered
|
||||
|
||||
|
||||
def _extract_store_classes(owner: Any) -> Dict[str, Type[BaseStore]]:
|
||||
discovered: Dict[str, Type[BaseStore]] = {}
|
||||
|
||||
def _add_candidate(key: Any, candidate: Any) -> None:
|
||||
if not inspect.isclass(candidate):
|
||||
return
|
||||
if candidate is BaseStore:
|
||||
return
|
||||
if not issubclass(candidate, BaseStore):
|
||||
return
|
||||
normalized = _normalize_store_type(str(key or candidate.__name__))
|
||||
if normalized:
|
||||
discovered[normalized] = candidate
|
||||
|
||||
if owner is None:
|
||||
return discovered
|
||||
|
||||
if inspect.isclass(owner):
|
||||
_add_candidate(None, owner)
|
||||
return discovered
|
||||
|
||||
if isinstance(owner, dict):
|
||||
for key, candidate in owner.items():
|
||||
_add_candidate(key, candidate)
|
||||
return discovered
|
||||
|
||||
if isinstance(owner, (list, tuple, set, frozenset)):
|
||||
for candidate in owner:
|
||||
_add_candidate(None, candidate)
|
||||
return discovered
|
||||
|
||||
try:
|
||||
for key, candidate in vars(owner).items():
|
||||
_add_candidate(key, candidate)
|
||||
except Exception:
|
||||
pass
|
||||
return discovered
|
||||
|
||||
|
||||
def _discover_plugin_store_class(store_type: str) -> Optional[Type[BaseStore]]:
|
||||
normalized = _normalize_store_type(store_type)
|
||||
if not normalized:
|
||||
return None
|
||||
|
||||
cached = _PLUGIN_DISCOVERED_CLASSES_CACHE.get(normalized, None)
|
||||
if normalized in _PLUGIN_DISCOVERED_CLASSES_CACHE:
|
||||
return cached
|
||||
|
||||
try:
|
||||
plugin_module = importlib.import_module(f"plugins.{normalized}")
|
||||
except Exception:
|
||||
_PLUGIN_DISCOVERED_CLASSES_CACHE[normalized] = None
|
||||
return None
|
||||
|
||||
discovered: Dict[str, Type[BaseStore]] = {}
|
||||
|
||||
backend_hook = getattr(plugin_module, "get_store_backend_classes", None)
|
||||
if callable(backend_hook):
|
||||
try:
|
||||
discovered.update(_extract_store_classes(backend_hook()))
|
||||
except Exception as exc:
|
||||
debug(f"[Store] Failed to load plugin store backends for '{normalized}': {exc}")
|
||||
|
||||
discovered.update(_extract_store_classes(getattr(plugin_module, "STORE_BACKENDS", None)))
|
||||
|
||||
if normalized not in discovered:
|
||||
discovered.update(_extract_store_classes(plugin_module))
|
||||
|
||||
resolved = discovered.get(normalized)
|
||||
if resolved is None and len(discovered) == 1:
|
||||
resolved = next(iter(discovered.values()))
|
||||
|
||||
_PLUGIN_DISCOVERED_CLASSES_CACHE[normalized] = resolved
|
||||
return resolved
|
||||
|
||||
|
||||
def _resolve_store_class(
|
||||
store_type: str,
|
||||
classes_by_type: Optional[Dict[str, Type[BaseStore]]] = None,
|
||||
) -> Optional[Type[BaseStore]]:
|
||||
normalized = _normalize_store_type(store_type)
|
||||
if not normalized:
|
||||
return None
|
||||
|
||||
plugin_resolved = _discover_plugin_store_class(normalized)
|
||||
if plugin_resolved is not None:
|
||||
return plugin_resolved
|
||||
|
||||
discovered = classes_by_type if classes_by_type is not None else _discover_store_classes()
|
||||
resolved = discovered.get(normalized)
|
||||
if resolved is not None:
|
||||
return resolved
|
||||
return None
|
||||
|
||||
|
||||
def _required_keys_for(store_cls: Type[BaseStore]) -> list[str]:
|
||||
# Support new config_schema() schema
|
||||
if hasattr(store_cls, "config_schema") and callable(store_cls.config_schema):
|
||||
@@ -170,7 +266,7 @@ class Store:
|
||||
store_type = _normalize_store_type(str(raw_store_type))
|
||||
if store_type == "folder":
|
||||
continue
|
||||
store_cls = classes_by_type.get(store_type)
|
||||
store_cls = _resolve_store_class(store_type, classes_by_type)
|
||||
if store_cls is None:
|
||||
# Skip provider-only names without debug warning
|
||||
if store_type not in _PROVIDER_ONLY_STORE_NAMES and not self._suppress_debug:
|
||||
@@ -373,7 +469,7 @@ def list_configured_backend_names(config: Optional[Dict[str, Any]]) -> list[str]
|
||||
if store_type == "folder" or store_type in _PROVIDER_ONLY_STORE_NAMES:
|
||||
continue
|
||||
|
||||
store_cls = classes_by_type.get(store_type)
|
||||
store_cls = _resolve_store_class(store_type, classes_by_type)
|
||||
if store_cls is None:
|
||||
continue
|
||||
|
||||
@@ -417,7 +513,7 @@ def get_backend_instance(config: Optional[Dict[str, Any]], backend_name: str, *,
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
store_type = _normalize_store_type(str(raw_store_type))
|
||||
store_cls = classes_by_type.get(store_type)
|
||||
store_cls = _resolve_store_class(store_type, classes_by_type)
|
||||
if store_cls is None:
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user