f
This commit is contained in:
@@ -1934,7 +1934,7 @@ class HydrusNetwork(Store):
|
||||
try:
|
||||
if service_key:
|
||||
# Mutate tags for many hashes in a single request
|
||||
client.mutate_tags_by_key(hash=hashes, service_key=service_key, add_tags=list(tag_tuple))
|
||||
client.mutate_tags_by_key(hashes=hashes, service_key=service_key, add_tags=list(tag_tuple))
|
||||
any_success = True
|
||||
continue
|
||||
except Exception as exc:
|
||||
|
||||
@@ -28,9 +28,7 @@ _DISCOVERED_CLASSES_CACHE: Optional[Dict[str, Type[BaseStore]]] = None
|
||||
|
||||
# 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>.
|
||||
_FAILED_BACKEND_CACHE: Dict[tuple[str,
|
||||
str],
|
||||
str] = {}
|
||||
_FAILED_BACKEND_CACHE: Dict[tuple[str, str], str] = {}
|
||||
|
||||
|
||||
def _normalize_store_type(value: str) -> str:
|
||||
@@ -63,13 +61,10 @@ def _discover_store_classes() -> Dict[str, Type[BaseStore]]:
|
||||
|
||||
import Store as store_pkg
|
||||
|
||||
discovered: Dict[str,
|
||||
Type[BaseStore]] = {}
|
||||
discovered: Dict[str, Type[BaseStore]] = {}
|
||||
for module_info in pkgutil.iter_modules(store_pkg.__path__):
|
||||
module_name = module_info.name
|
||||
if module_name in {"__init__",
|
||||
"_base",
|
||||
"registry"}:
|
||||
if module_name in {"__init__", "_base", "registry"}:
|
||||
continue
|
||||
|
||||
try:
|
||||
@@ -122,10 +117,7 @@ def _required_keys_for(store_cls: Type[BaseStore]) -> list[str]:
|
||||
)
|
||||
|
||||
|
||||
def _build_kwargs(store_cls: Type[BaseStore],
|
||||
instance_name: str,
|
||||
instance_config: Any) -> Dict[str,
|
||||
Any]:
|
||||
def _build_kwargs(store_cls: Type[BaseStore], instance_name: str, instance_config: Any) -> Dict[str, Any]:
|
||||
if isinstance(instance_config, dict):
|
||||
cfg_dict = dict(instance_config)
|
||||
else:
|
||||
@@ -134,13 +126,10 @@ def _build_kwargs(store_cls: Type[BaseStore],
|
||||
required = _required_keys_for(store_cls)
|
||||
|
||||
# If NAME is required but not present, allow the instance key to provide it.
|
||||
if (any(_normalize_config_key(k) == "NAME" for k in required)
|
||||
and _get_case_insensitive(cfg_dict,
|
||||
"NAME") is None):
|
||||
if (any(_normalize_config_key(k) == "NAME" for k in required) and _get_case_insensitive(cfg_dict, "NAME") is None):
|
||||
cfg_dict["NAME"] = str(instance_name)
|
||||
|
||||
kwargs: Dict[str,
|
||||
Any] = {}
|
||||
kwargs: Dict[str, Any] = {}
|
||||
missing: list[str] = []
|
||||
for key in required:
|
||||
value = _get_case_insensitive(cfg_dict, key)
|
||||
@@ -257,8 +246,7 @@ class Store:
|
||||
|
||||
# Convenience normalization for filesystem-like paths.
|
||||
for key in list(kwargs.keys()):
|
||||
if _normalize_config_key(key) in {"PATH",
|
||||
"LOCATION"}:
|
||||
if _normalize_config_key(key) in {"PATH", "LOCATION"}:
|
||||
kwargs[key] = str(expand_path(kwargs[key]))
|
||||
|
||||
backend = store_cls(**kwargs)
|
||||
@@ -283,8 +271,50 @@ class Store:
|
||||
f"[Store] Failed to register {store_cls.__name__} instance '{instance_name}': {exc}"
|
||||
)
|
||||
|
||||
def _resolve_backend_name(self,
|
||||
backend_name: str) -> tuple[Optional[str], Optional[str]]:
|
||||
def _resolve_backend_name(self, backend_name: str) -> tuple[Optional[str], Optional[str]]:
|
||||
requested = str(backend_name or "")
|
||||
if requested in self._backends:
|
||||
return requested, None
|
||||
|
||||
requested_norm = _normalize_store_type(requested)
|
||||
|
||||
ci_matches = [
|
||||
name for name in self._backends
|
||||
if _normalize_store_type(name) == requested_norm
|
||||
]
|
||||
if len(ci_matches) == 1:
|
||||
return ci_matches[0], None
|
||||
if len(ci_matches) > 1:
|
||||
return None, f"Ambiguous store alias '{backend_name}' matches {ci_matches}"
|
||||
|
||||
type_matches = [
|
||||
name for name, store_type in self._backend_types.items()
|
||||
if store_type == requested_norm
|
||||
]
|
||||
if len(type_matches) == 1:
|
||||
return type_matches[0], None
|
||||
if len(type_matches) > 1:
|
||||
return None, (
|
||||
f"Ambiguous store alias '{backend_name}' matches type '{requested_norm}': {type_matches}"
|
||||
)
|
||||
|
||||
prefix_matches = [
|
||||
name for name, store_type in self._backend_types.items()
|
||||
if store_type.startswith(requested_norm)
|
||||
]
|
||||
if len(prefix_matches) == 1:
|
||||
return prefix_matches[0], None
|
||||
if len(prefix_matches) > 1:
|
||||
return None, (
|
||||
f"Ambiguous store alias '{backend_name}' matches type prefix '{requested_norm}': {prefix_matches}"
|
||||
)
|
||||
|
||||
return None, None
|
||||
|
||||
# get_backend_instance implementation moved to the bottom of this file to avoid
|
||||
# instantiating all backends during startup (see function `get_backend_instance`).
|
||||
|
||||
def _resolve_backend_name(self, backend_name: str) -> tuple[Optional[str], Optional[str]]:
|
||||
requested = str(backend_name or "")
|
||||
if requested in self._backends:
|
||||
return requested, None
|
||||
@@ -461,3 +491,85 @@ def list_configured_backend_names(config: Optional[Dict[str, Any]]) -> list[str]
|
||||
return sorted(set(names))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def get_backend_instance(config: Optional[Dict[str, Any]], backend_name: str, *, suppress_debug: bool = False) -> Optional[BaseStore]:
|
||||
"""Instantiate and return a single store backend by configured name.
|
||||
|
||||
This avoids creating all configured backends (and opening their DBs)
|
||||
when only a single backend is needed (common in `get-file`/`get-metadata`).
|
||||
The function first tries a lightweight match against raw config NAME/value to
|
||||
avoid calling `_build_kwargs` (which can raise if keys are missing).
|
||||
Returns None when no matching backend is found or instantiation fails.
|
||||
"""
|
||||
if not backend_name:
|
||||
return None
|
||||
store_cfg = (config or {}).get("store") or {}
|
||||
if not isinstance(store_cfg, dict):
|
||||
return None
|
||||
classes_by_type = _discover_store_classes()
|
||||
desired = str(backend_name or "").strip().lower()
|
||||
|
||||
for raw_store_type, instances in store_cfg.items():
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
store_type = _normalize_store_type(str(raw_store_type))
|
||||
store_cls = classes_by_type.get(store_type)
|
||||
if store_cls is None:
|
||||
continue
|
||||
|
||||
# Fast path: match using raw 'NAME' or 'name' in config without building full kwargs
|
||||
for instance_name, instance_cfg in instances.items():
|
||||
candidate_alias = None
|
||||
if isinstance(instance_cfg, dict):
|
||||
candidate_alias = (
|
||||
instance_cfg.get("NAME") or instance_cfg.get("name")
|
||||
)
|
||||
candidate_alias = str(candidate_alias or instance_name).strip()
|
||||
if candidate_alias.lower() == desired:
|
||||
try:
|
||||
kwargs = _build_kwargs(store_cls, str(instance_name), instance_cfg)
|
||||
except Exception as exc:
|
||||
if not suppress_debug:
|
||||
debug(f"[Store] Can't build kwargs for '{instance_name}' ({store_type}): {exc}")
|
||||
return None
|
||||
try:
|
||||
for key in list(kwargs.keys()):
|
||||
if _normalize_config_key(key) in {"PATH", "LOCATION"}:
|
||||
kwargs[key] = str(expand_path(kwargs[key]))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
backend = store_cls(**kwargs)
|
||||
return backend
|
||||
except Exception as exc:
|
||||
if not suppress_debug:
|
||||
debug(f"[Store] Failed to instantiate backend '{candidate_alias}': {exc}")
|
||||
return None
|
||||
|
||||
# Fallback: build kwargs for each instance and compare resolved NAME
|
||||
for instance_name, instance_cfg in instances.items():
|
||||
try:
|
||||
kwargs = _build_kwargs(store_cls, str(instance_name), instance_cfg)
|
||||
except Exception:
|
||||
continue
|
||||
alias = str(kwargs.get("NAME") or instance_name).strip()
|
||||
if alias.lower() != desired:
|
||||
continue
|
||||
try:
|
||||
for key in list(kwargs.keys()):
|
||||
if _normalize_config_key(key) in {"PATH", "LOCATION"}:
|
||||
kwargs[key] = str(expand_path(kwargs[key]))
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
backend = store_cls(**kwargs)
|
||||
return backend
|
||||
except Exception as exc:
|
||||
if not suppress_debug:
|
||||
debug(f"[Store] Failed to instantiate backend '{alias}': {exc}")
|
||||
return None
|
||||
|
||||
if not suppress_debug:
|
||||
debug(f"[Store] Backend '{backend_name}' not found in config")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user