continuing refactor
This commit is contained in:
+1
-1
@@ -138,7 +138,7 @@ def _validate_add_note_requires_add_file_order(raw: str) -> Optional[SyntaxError
|
||||
# If add-note occurs before any add-file stage, it must be explicitly targeted.
|
||||
if any(pos > i for pos in add_file_positions):
|
||||
has_hash = _has_flag(tokens, "-hash", "--hash")
|
||||
has_store = _has_flag(tokens, "-store", "--store")
|
||||
has_store = _has_flag(tokens, "-instance", "--instance")
|
||||
|
||||
# Also accept explicit targeting via -query "store:<store> hash:<sha256> ...".
|
||||
query_val = _get_flag_value(tokens, "-query", "--query")
|
||||
|
||||
+165
-102
@@ -97,16 +97,27 @@ def _log_config_load_summary(config: Dict[str, Any]) -> None:
|
||||
plugin_block = config.get("plugin")
|
||||
if not isinstance(plugin_block, dict):
|
||||
plugin_block = config.get("provider")
|
||||
provs = list(plugin_block.keys()) if isinstance(plugin_block, dict) else []
|
||||
stores = list(config.get("store", {}).keys()) if isinstance(config.get("store"), dict) else []
|
||||
if isinstance(plugin_block, dict):
|
||||
# Count distinct plugin names; note multi-instance plugins appear once per name
|
||||
plugin_names = list(plugin_block.keys())
|
||||
# Count total configured instances across all plugins
|
||||
total_instances = sum(
|
||||
len(v) if isinstance(v, dict) and all(isinstance(x, dict) for x in v.values()) else 1
|
||||
for v in plugin_block.values()
|
||||
if isinstance(v, dict)
|
||||
)
|
||||
else:
|
||||
plugin_names, total_instances = [], 0
|
||||
mtime = None
|
||||
try:
|
||||
mtime = datetime.datetime.fromtimestamp(db.db_path.stat().st_mtime, datetime.timezone.utc).isoformat().replace('+00:00', 'Z')
|
||||
except Exception:
|
||||
mtime = None
|
||||
plugins_str = ', '.join(plugin_names[:10]) + ('...' if len(plugin_names) > 10 else '')
|
||||
summary = (
|
||||
f"Loaded config from {db.db_path.name}: plugins={len(provs)} ({', '.join(provs[:10])}{'...' if len(provs)>10 else ''}), "
|
||||
f"stores={len(stores)} ({', '.join(stores[:10])}{'...' if len(stores)>10 else ''}), mtime={mtime}"
|
||||
f"Loaded config from {db.db_path.name}: "
|
||||
f"plugins={len(plugin_names)} ({plugins_str}), "
|
||||
f"instances={total_instances}, mtime={mtime}"
|
||||
)
|
||||
log(summary)
|
||||
except Exception:
|
||||
@@ -254,37 +265,37 @@ def set_nested_config_value(
|
||||
def get_hydrus_instance(
|
||||
config: Dict[str, Any], instance_name: str = "home"
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific Hydrus instance config by name.
|
||||
"""Get a specific Hydrus instance config by name from plugin/provider config."""
|
||||
def _lookup_in(source: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
if not isinstance(source, dict) or not source:
|
||||
return None
|
||||
instance = source.get(instance_name)
|
||||
if isinstance(instance, dict):
|
||||
return instance
|
||||
target = str(instance_name or "").lower()
|
||||
for name, conf in source.items():
|
||||
if isinstance(conf, dict) and str(name).lower() == target:
|
||||
return conf
|
||||
keys = sorted(source.keys())
|
||||
for key in keys:
|
||||
if not str(key or "").startswith("new_"):
|
||||
candidate = source.get(key)
|
||||
if isinstance(candidate, dict):
|
||||
return candidate
|
||||
first_key = keys[0] if keys else None
|
||||
candidate = source.get(first_key) if first_key else None
|
||||
return candidate if isinstance(candidate, dict) else None
|
||||
|
||||
Supports modern config plus a fallback when no exact match exists.
|
||||
"""
|
||||
store = config.get("store", {})
|
||||
if not isinstance(store, dict):
|
||||
return None
|
||||
|
||||
hydrusnetwork = store.get("hydrusnetwork", {})
|
||||
if not isinstance(hydrusnetwork, dict) or not hydrusnetwork:
|
||||
return None
|
||||
|
||||
instance = hydrusnetwork.get(instance_name)
|
||||
if isinstance(instance, dict):
|
||||
return instance
|
||||
|
||||
target = str(instance_name or "").lower()
|
||||
for name, conf in hydrusnetwork.items():
|
||||
if isinstance(conf, dict) and str(name).lower() == target:
|
||||
return conf
|
||||
|
||||
keys = sorted(hydrusnetwork.keys())
|
||||
for key in keys:
|
||||
if not str(key or "").startswith("new_"):
|
||||
candidate = hydrusnetwork.get(key)
|
||||
if isinstance(candidate, dict):
|
||||
return candidate
|
||||
first_key = keys[0]
|
||||
candidate = hydrusnetwork.get(first_key)
|
||||
if isinstance(candidate, dict):
|
||||
return candidate
|
||||
# New format: config["plugin"]["hydrusnetwork"] or config["provider"]["hydrusnetwork"]
|
||||
# (both point to the same dict after normalization)
|
||||
for section in ("plugin", "provider"):
|
||||
section_cfg = config.get(section)
|
||||
if isinstance(section_cfg, dict):
|
||||
hydrus_cfg = section_cfg.get("hydrusnetwork")
|
||||
if isinstance(hydrus_cfg, dict):
|
||||
result = _lookup_in(hydrus_cfg)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
@@ -293,7 +304,7 @@ def get_hydrus_access_key(config: Dict[str, Any], instance_name: str = "home") -
|
||||
"""Get Hydrus access key for an instance.
|
||||
|
||||
Config format:
|
||||
- config["store"]["hydrusnetwork"][name]["API"]
|
||||
- config["plugin"]["hydrusnetwork"][name]["API"]
|
||||
|
||||
Args:
|
||||
config: Configuration dict
|
||||
@@ -314,7 +325,7 @@ def get_hydrus_url(config: Dict[str, Any], instance_name: str = "home") -> Optio
|
||||
"""Get Hydrus URL for an instance.
|
||||
|
||||
Config format:
|
||||
- config["store"]["hydrusnetwork"][name]["URL"]
|
||||
- config["plugin"]["hydrusnetwork"][name]["URL"]
|
||||
|
||||
Args:
|
||||
config: Configuration dict
|
||||
@@ -438,9 +449,7 @@ def get_local_storage_path(config: Dict[str, Any]) -> Optional[Path]:
|
||||
def get_debrid_api_key(config: Dict[str, Any], service: str = "All-debrid") -> Optional[str]:
|
||||
"""Get Debrid API key from config.
|
||||
|
||||
Config format:
|
||||
- config["store"]["debrid"][<name>]["api_key"]
|
||||
where <name> is the store name (e.g. "all-debrid")
|
||||
Checks the plugin/provider block first (canonical format).
|
||||
|
||||
Args:
|
||||
config: Configuration dict
|
||||
@@ -449,23 +458,27 @@ def get_debrid_api_key(config: Dict[str, Any], service: str = "All-debrid") -> O
|
||||
Returns:
|
||||
API key string if found, None otherwise
|
||||
"""
|
||||
store = config.get("store", {})
|
||||
if not isinstance(store, dict):
|
||||
return None
|
||||
# 1) Canonical plugin/provider block: config["plugin"]["alldebrid"]["api_key"]
|
||||
provider_block = config.get("provider") or config.get("plugin")
|
||||
if isinstance(provider_block, dict):
|
||||
alldebrid_entry = provider_block.get("alldebrid")
|
||||
if isinstance(alldebrid_entry, dict):
|
||||
for k in ("api_key", "API_KEY", "apikey", "APIKEY"):
|
||||
val = alldebrid_entry.get(k)
|
||||
if isinstance(val, str) and val.strip():
|
||||
return val.strip()
|
||||
|
||||
debrid_config = store.get("debrid", {})
|
||||
if not isinstance(debrid_config, dict):
|
||||
return None
|
||||
|
||||
service_key = str(service).strip().lower()
|
||||
entry = debrid_config.get(service_key)
|
||||
|
||||
if isinstance(entry, dict):
|
||||
api_key = entry.get("api_key")
|
||||
return str(api_key).strip() if api_key else None
|
||||
|
||||
if isinstance(entry, str):
|
||||
return entry.strip() or None
|
||||
# 2) Migrated legacy debrid plugin entry: config["plugin"]["debrid"]["all-debrid"]["api_key"]
|
||||
if isinstance(provider_block, dict):
|
||||
service_key = str(service).strip().lower()
|
||||
debrid_plugin = provider_block.get("debrid")
|
||||
if isinstance(debrid_plugin, dict):
|
||||
entry = debrid_plugin.get(service_key)
|
||||
if isinstance(entry, dict):
|
||||
api_key = entry.get("api_key")
|
||||
return str(api_key).strip() if api_key else None
|
||||
if isinstance(entry, str):
|
||||
return entry.strip() or None
|
||||
|
||||
return None
|
||||
|
||||
@@ -635,6 +648,25 @@ def _normalize_plugin_config_aliases(config: Dict[str, Any]) -> None:
|
||||
if normalized_key and normalized_key not in normalized_provider:
|
||||
normalized_provider[normalized_key] = value
|
||||
|
||||
# Fold legacy config["store"] entries into the plugin namespace.
|
||||
# store format: {type: {instance_name: {key: val}}} — multi-instance.
|
||||
# After folding, remove config["store"] so it is no longer consulted.
|
||||
store_block = config.pop("store", None)
|
||||
if isinstance(store_block, dict):
|
||||
for store_type, instances in store_block.items():
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
normalized_key = _normalize_provider_name(store_type)
|
||||
if not normalized_key:
|
||||
continue
|
||||
existing = normalized_provider.get(normalized_key)
|
||||
if not isinstance(existing, dict):
|
||||
existing = {}
|
||||
normalized_provider[normalized_key] = existing
|
||||
for instance_name, settings in instances.items():
|
||||
if isinstance(settings, dict) and instance_name not in existing:
|
||||
existing[instance_name] = dict(settings)
|
||||
|
||||
if normalized_provider:
|
||||
config["provider"] = normalized_provider
|
||||
config["plugin"] = normalized_provider
|
||||
@@ -658,6 +690,11 @@ def _extract_api_key(value: Any) -> Optional[str]:
|
||||
|
||||
|
||||
def _sync_alldebrid_api_key(config: Dict[str, Any]) -> None:
|
||||
"""Ensure AllDebrid API key is consistently stored in config[\"plugin\"][\"alldebrid\"].
|
||||
|
||||
Previously this function also synced to config[\"store\"][\"debrid\"]. That path
|
||||
is no longer used; only the plugin namespace is written.
|
||||
"""
|
||||
if not isinstance(config, dict):
|
||||
return
|
||||
|
||||
@@ -680,38 +717,39 @@ def _sync_alldebrid_api_key(config: Dict[str, Any]) -> None:
|
||||
provider_section = {"api_key": provider_key}
|
||||
providers["alldebrid"] = provider_section
|
||||
|
||||
store_block = config.get("store")
|
||||
if not isinstance(store_block, dict):
|
||||
store_block = {}
|
||||
config["store"] = store_block
|
||||
# If no key found in provider block, check for a migrated debrid plugin entry.
|
||||
# (rows_to_config migrates store.debrid.all-debrid → plugin.debrid.all-debrid)
|
||||
if not provider_key:
|
||||
plugin_block = config.get("plugin") or providers
|
||||
debrid_plugin = plugin_block.get("debrid") if isinstance(plugin_block, dict) else None
|
||||
if isinstance(debrid_plugin, dict):
|
||||
service_entry = debrid_plugin.get("all-debrid")
|
||||
legacy_key = _extract_api_key(service_entry) if service_entry else None
|
||||
if legacy_key:
|
||||
if provider_section is None:
|
||||
provider_section = {}
|
||||
providers["alldebrid"] = provider_section
|
||||
provider_section.setdefault("api_key", legacy_key)
|
||||
|
||||
debrid_block = store_block.get("debrid")
|
||||
store_key = None
|
||||
if isinstance(debrid_block, dict):
|
||||
service_entry = debrid_block.get("all-debrid")
|
||||
if isinstance(service_entry, dict):
|
||||
store_key = _extract_api_key(service_entry)
|
||||
elif isinstance(service_entry, str):
|
||||
store_key = service_entry.strip()
|
||||
if store_key:
|
||||
debrid_block["all-debrid"] = {"api_key": store_key}
|
||||
else:
|
||||
debrid_block = None
|
||||
|
||||
if provider_key:
|
||||
if debrid_block is None:
|
||||
debrid_block = {}
|
||||
store_block["debrid"] = debrid_block
|
||||
service_section = debrid_block.get("all-debrid")
|
||||
if not isinstance(service_section, dict):
|
||||
service_section = {}
|
||||
debrid_block["all-debrid"] = service_section
|
||||
service_section["api_key"] = provider_key
|
||||
elif store_key:
|
||||
if provider_section is None:
|
||||
provider_section = {}
|
||||
providers["alldebrid"] = provider_section
|
||||
provider_section["api_key"] = store_key
|
||||
|
||||
def _is_multi_instance_plugin_config(value: Any) -> bool:
|
||||
"""Return True if `value` looks like a multi-instance plugin config (dict-of-dicts).
|
||||
|
||||
Multi-instance plugins store their configuration as::
|
||||
|
||||
{<instance_name>: {key: value, ...}, ...}
|
||||
|
||||
Single-instance plugins store their config as a flat dict::
|
||||
|
||||
{key: value, ...}
|
||||
|
||||
We detect multi-instance by checking whether ALL values are themselves dicts
|
||||
(and the outer dict is non-empty). An empty dict is treated as single-instance.
|
||||
"""
|
||||
if not isinstance(value, dict) or not value:
|
||||
return False
|
||||
return all(isinstance(v, dict) for v in value.values())
|
||||
|
||||
|
||||
def _flatten_config_entries(config: Dict[str, Any]) -> Dict[Tuple[str, str, str, str], Any]:
|
||||
@@ -719,18 +757,35 @@ def _flatten_config_entries(config: Dict[str, Any]) -> Dict[Tuple[str, str, str,
|
||||
_normalize_plugin_config_aliases(config)
|
||||
for key, value in config.items():
|
||||
if key == 'plugin':
|
||||
# plugin == provider after normalization; skip duplicate
|
||||
continue
|
||||
if key in ('store', 'provider', 'tool') and isinstance(value, dict):
|
||||
if key == 'provider' and isinstance(value, dict):
|
||||
for subtype, plugin_cfg in value.items():
|
||||
if not isinstance(plugin_cfg, dict):
|
||||
continue
|
||||
if _is_multi_instance_plugin_config(plugin_cfg):
|
||||
# Multi-instance: {instance_name: {key: val}}
|
||||
for instance_name, settings in plugin_cfg.items():
|
||||
if not isinstance(settings, dict):
|
||||
continue
|
||||
for k, v in settings.items():
|
||||
entries[('plugin', subtype, instance_name, k)] = v
|
||||
else:
|
||||
# Single-instance: {key: val}
|
||||
for k, v in plugin_cfg.items():
|
||||
entries[('provider', subtype, 'default', k)] = v
|
||||
elif key in ('store', 'tool') and isinstance(value, dict):
|
||||
for subtype, instances in value.items():
|
||||
if not isinstance(instances, dict):
|
||||
continue
|
||||
if key == 'store':
|
||||
# Legacy store: migrate to plugin category
|
||||
for name, settings in instances.items():
|
||||
if not isinstance(settings, dict):
|
||||
continue
|
||||
for k, v in settings.items():
|
||||
entries[(key, subtype, name, k)] = v
|
||||
else:
|
||||
entries[('plugin', subtype, name, k)] = v
|
||||
else: # tool
|
||||
for k, v in instances.items():
|
||||
entries[(key, subtype, 'default', k)] = v
|
||||
elif not key.startswith('_') and value is not None:
|
||||
@@ -763,12 +818,23 @@ def _config_from_flattened_entries(
|
||||
continue
|
||||
|
||||
if category == "store":
|
||||
store_block = config.setdefault("store", {})
|
||||
subtype_block = store_block.setdefault(subtype, {})
|
||||
# Legacy: migrate to plugin namespace at reconstitution time
|
||||
plugin_block = config.setdefault("plugin", {})
|
||||
subtype_block = plugin_block.setdefault(subtype, {})
|
||||
item_block = subtype_block.setdefault(item_name, {})
|
||||
item_block[key] = value
|
||||
continue
|
||||
|
||||
if category == "plugin":
|
||||
plugin_block = config.setdefault("plugin", {})
|
||||
subtype_block = plugin_block.setdefault(subtype, {})
|
||||
if item_name == "default":
|
||||
subtype_block[key] = value
|
||||
else:
|
||||
item_block = subtype_block.setdefault(item_name, {})
|
||||
item_block[key] = value
|
||||
continue
|
||||
|
||||
if category in {"provider", "tool"}:
|
||||
category_block = config.setdefault(category, {})
|
||||
subtype_block = category_block.setdefault(subtype, {})
|
||||
@@ -827,18 +893,7 @@ def _extract_expected_alldebrid_key(config: Dict[str, Any]) -> Optional[str]:
|
||||
elif isinstance(entry, str) and entry.strip():
|
||||
expected_key = entry.strip()
|
||||
if not expected_key:
|
||||
store_block = config.get("store", {}) if isinstance(config, dict) else {}
|
||||
debrid = store_block.get("debrid") if isinstance(store_block, dict) else None
|
||||
if isinstance(debrid, dict):
|
||||
srv = debrid.get("all-debrid")
|
||||
if isinstance(srv, dict):
|
||||
for k in ("api_key", "API_KEY", "apikey", "APIKEY"):
|
||||
v = srv.get(k)
|
||||
if isinstance(v, str) and v.strip():
|
||||
expected_key = v.strip()
|
||||
break
|
||||
elif isinstance(srv, str) and srv.strip():
|
||||
expected_key = srv.strip()
|
||||
expected_key = get_debrid_api_key(config, service="All-debrid")
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to determine expected AllDebrid key: %s", exc, exc_info=True)
|
||||
expected_key = None
|
||||
@@ -853,6 +908,14 @@ def load_config(*, emit_summary: bool = True) -> Dict[str, Any]:
|
||||
_CONFIG_SUMMARY_PENDING = False
|
||||
return _CONFIG_CACHE
|
||||
|
||||
# One-time DB migration: move category='store' rows to category='plugin'.
|
||||
# This is idempotent — a no-op if no store rows exist.
|
||||
try:
|
||||
from SYS.database import migrate_store_category_to_plugin
|
||||
migrate_store_category_to_plugin()
|
||||
except Exception:
|
||||
logger.debug("Store→plugin DB migration skipped or failed", exc_info=True)
|
||||
|
||||
# Load strictly from database
|
||||
db_config = get_config_all()
|
||||
if db_config:
|
||||
|
||||
+45
-1
@@ -510,7 +510,10 @@ def rows_to_config(rows) -> Dict[str, Any]:
|
||||
sub_dict = cat_dict.setdefault(sub, {})
|
||||
sub_dict[key] = parsed_val
|
||||
elif cat == 'store':
|
||||
cat_dict = config.setdefault(cat, {})
|
||||
# Migrate legacy store rows into the unified plugin namespace.
|
||||
# store config used a 4-level path: (store, type, instance_name, key).
|
||||
# Plugin config uses: config["plugin"][type][instance_name][key].
|
||||
cat_dict = config.setdefault('plugin', {})
|
||||
sub_dict = cat_dict.setdefault(sub, {})
|
||||
name_dict = sub_dict.setdefault(name, {})
|
||||
name_dict[key] = parsed_val
|
||||
@@ -520,11 +523,52 @@ def rows_to_config(rows) -> Dict[str, Any]:
|
||||
return config
|
||||
|
||||
|
||||
def migrate_store_category_to_plugin() -> int:
|
||||
"""One-time migration: re-key category='store' DB rows to category='plugin'.
|
||||
|
||||
The 'store' category used ``(store, type, instance_name, key)`` tuples;
|
||||
the unified plugin system uses the same 4-level path under category='plugin'.
|
||||
Existing 'plugin' rows for the same (subtype, item_name, key) are overwritten.
|
||||
|
||||
Returns the number of rows that were migrated (0 if already migrated).
|
||||
"""
|
||||
try:
|
||||
count_row = db.fetchone(
|
||||
"SELECT COUNT(*) AS n FROM config WHERE category='store' AND subtype != 'folder'"
|
||||
)
|
||||
count = int(count_row['n']) if count_row else 0
|
||||
if count == 0:
|
||||
# Also clean up any lingering folder-store rows
|
||||
db.execute("DELETE FROM config WHERE category='store'")
|
||||
with db._conn_lock:
|
||||
db.conn.commit()
|
||||
return 0
|
||||
# Copy store rows to plugin, replacing any pre-existing plugin rows for
|
||||
# the same (subtype, item_name, key), then delete the old store rows.
|
||||
db.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO config (category, subtype, item_name, key, value)
|
||||
SELECT 'plugin', subtype, item_name, key, value
|
||||
FROM config
|
||||
WHERE category = 'store' AND subtype != 'folder'
|
||||
"""
|
||||
)
|
||||
db.execute("DELETE FROM config WHERE category = 'store'")
|
||||
with db._conn_lock:
|
||||
db.conn.commit()
|
||||
logger.info("Migrated %d config rows from category='store' to category='plugin'", count)
|
||||
return count
|
||||
except Exception:
|
||||
logger.exception("Failed to migrate store config rows to plugin category")
|
||||
return 0
|
||||
|
||||
|
||||
def get_config_all() -> Dict[str, Any]:
|
||||
"""Retrieve all configuration from the database in the legacy dict format."""
|
||||
rows = db.fetchall("SELECT category, subtype, item_name, key, value FROM config")
|
||||
return rows_to_config(rows)
|
||||
|
||||
|
||||
# Worker Management Methods for medios.db
|
||||
|
||||
def _worker_db_connect(timeout: float = 0.75) -> sqlite3.Connection:
|
||||
|
||||
+2
-2
@@ -1272,7 +1272,7 @@ class PipelineExecutor:
|
||||
"""Guard against running add-relationship on unstored download-file results.
|
||||
|
||||
Intended UX:
|
||||
download-file ... | add-file -store <store> | add-relationship
|
||||
download-file ... | add-file -instance <store> | add-relationship
|
||||
|
||||
Rationale:
|
||||
download-file outputs items that may not yet have a stable store+hash.
|
||||
@@ -1305,7 +1305,7 @@ class PipelineExecutor:
|
||||
print(
|
||||
"Pipeline order error: when using download-file with add-relationship, "
|
||||
"add-relationship must come after add-file (so items are stored and have store+hash).\n"
|
||||
"Example: download-file <...> | add-file -store <store> | add-relationship\n"
|
||||
"Example: download-file <...> | add-file -instance <store> | add-relationship\n"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
+41
-31
@@ -7,7 +7,6 @@ from typing import Any, Dict, Iterable, List, Optional
|
||||
|
||||
from SYS.config import global_config
|
||||
from ProviderCore.registry import get_plugin_class, list_plugins
|
||||
from Store.registry import _discover_store_classes, _required_keys_for, _resolve_store_class
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,10 +53,16 @@ def _call_schema(owner: Any, label: str) -> List[ConfigField]:
|
||||
|
||||
|
||||
def get_store_schema(store_type: str) -> List[ConfigField]:
|
||||
cls = _resolve_store_class(str(store_type or "").strip())
|
||||
if cls is None:
|
||||
return []
|
||||
return _call_schema(cls, f"store '{store_type}'")
|
||||
"""Return config schema for a store type.
|
||||
|
||||
After the store→plugin migration, store types are plugins. We look up the
|
||||
plugin schema by name; if not found we return an empty list.
|
||||
"""
|
||||
normalized = str(store_type or "").strip()
|
||||
# Strip a legacy "store-" prefix so callers using the old type name still work
|
||||
if normalized.startswith("store-"):
|
||||
normalized = normalized[len("store-"):]
|
||||
return get_plugin_schema(normalized)
|
||||
|
||||
|
||||
def get_plugin_schema(plugin_name: str) -> List[ConfigField]:
|
||||
@@ -84,6 +89,10 @@ def get_item_schema(item_type: str, item_name: str) -> List[ConfigField]:
|
||||
normalized_name = str(item_name or "").strip()
|
||||
if normalized_type.startswith("store-"):
|
||||
return get_store_schema(normalized_type.replace("store-", "", 1))
|
||||
if normalized_type.startswith("plugin-"):
|
||||
# Multi-instance plugin: plugin-{ptype}; item_name is the instance name
|
||||
ptype = normalized_type[len("plugin-"):]
|
||||
return get_plugin_schema(ptype)
|
||||
if normalized_type in {"provider", "plugin"}:
|
||||
return get_plugin_schema(normalized_name)
|
||||
if normalized_type == "tool":
|
||||
@@ -104,23 +113,14 @@ def get_global_schema_map() -> Dict[str, ConfigField]:
|
||||
|
||||
|
||||
def build_default_store_config(store_type: str, instance_name: str) -> Dict[str, Any]:
|
||||
"""Build a default config dict for a new store/multi-instance plugin entry."""
|
||||
config: Dict[str, Any] = {"NAME": instance_name}
|
||||
schema = get_store_schema(store_type)
|
||||
if schema:
|
||||
for field in schema:
|
||||
key = field["key"]
|
||||
if key.upper() == "NAME":
|
||||
continue
|
||||
config[key] = field.get("default", "")
|
||||
return config
|
||||
|
||||
cls = _resolve_store_class(str(store_type or "").strip())
|
||||
if cls is None:
|
||||
return config
|
||||
for required_key in _required_keys_for(cls):
|
||||
if required_key.upper() == "NAME":
|
||||
for field in schema:
|
||||
key = field["key"]
|
||||
if key.upper() == "NAME":
|
||||
continue
|
||||
config[required_key] = ""
|
||||
config[key] = field.get("default", "")
|
||||
return config
|
||||
|
||||
|
||||
@@ -170,12 +170,16 @@ def get_required_config_keys(item_type: str, item_name: str) -> List[str]:
|
||||
if field.get("required"):
|
||||
_add_key(field.get("key"))
|
||||
|
||||
if normalized_type.startswith("store-"):
|
||||
store_type = normalized_type.replace("store-", "", 1)
|
||||
cls = _resolve_store_class(store_type)
|
||||
if cls is not None:
|
||||
for required_key in _required_keys_for(cls):
|
||||
_add_key(required_key)
|
||||
if normalized_type.startswith("plugin-") or normalized_type.startswith("store-"):
|
||||
# Multi-instance plugin (plugin-{ptype}) or legacy store-{type}: look up by plugin name
|
||||
ptype = normalized_type.replace("plugin-", "", 1).replace("store-", "", 1)
|
||||
plugin_class = get_plugin_class(ptype)
|
||||
if plugin_class is not None:
|
||||
try:
|
||||
for required_key in plugin_class.required_config_keys():
|
||||
_add_key(required_key)
|
||||
except Exception:
|
||||
logger.exception("Failed to load required config keys for plugin '%s'", ptype)
|
||||
elif normalized_type in {"provider", "plugin"}:
|
||||
plugin_class = get_plugin_class(normalized_name)
|
||||
if plugin_class is not None:
|
||||
@@ -189,18 +193,24 @@ def get_required_config_keys(item_type: str, item_name: str) -> List[str]:
|
||||
|
||||
|
||||
def get_configurable_store_types() -> List[str]:
|
||||
"""Return configurable multi-instance plugin types (formerly 'store types')."""
|
||||
from ProviderCore.registry import REGISTRY
|
||||
options: List[str] = []
|
||||
for store_type in _discover_store_classes().keys():
|
||||
if get_store_schema(store_type):
|
||||
options.append(str(store_type))
|
||||
for info in REGISTRY.iter_plugins():
|
||||
plugin_cls = info.plugin_class
|
||||
if getattr(plugin_cls, 'MULTI_INSTANCE', False) and get_plugin_schema(info.canonical_name):
|
||||
options.append(info.canonical_name)
|
||||
return sorted(set(options))
|
||||
|
||||
|
||||
def get_configurable_plugin_types() -> List[str]:
|
||||
"""Return all plugin types that can be configured: those with a schema or MULTI_INSTANCE flag."""
|
||||
from ProviderCore.registry import REGISTRY
|
||||
options: List[str] = []
|
||||
for plugin_name in list_plugins().keys():
|
||||
if get_plugin_schema(plugin_name):
|
||||
options.append(str(plugin_name))
|
||||
for info in REGISTRY.iter_plugins():
|
||||
plugin_cls = info.plugin_class
|
||||
if get_plugin_schema(info.canonical_name) or getattr(plugin_cls, 'MULTI_INSTANCE', False):
|
||||
options.append(info.canonical_name)
|
||||
return sorted(set(options))
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ def show_plugin_config_panel(
|
||||
"""Show a Rich panel explaining how to configure plugins."""
|
||||
from rich.table import Table as RichTable
|
||||
from rich.console import Group
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
if isinstance(plugin_names, str):
|
||||
plugins = [p.strip() for p in plugin_names.split(",")]
|
||||
@@ -127,6 +129,8 @@ def show_store_config_panel(
|
||||
"""Show a Rich panel explaining how to configure storage backends."""
|
||||
from rich.table import Table as RichTable
|
||||
from rich.console import Group
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
if isinstance(store_names, str):
|
||||
stores = [s.strip() for s in store_names.split(",")]
|
||||
@@ -160,6 +164,8 @@ def show_available_plugins_panel(plugin_names: List[str]) -> None:
|
||||
"""Show a Rich panel listing available/configured plugins."""
|
||||
from rich.columns import Columns
|
||||
from rich.console import Group
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
if not plugin_names:
|
||||
return
|
||||
|
||||
@@ -45,7 +45,7 @@ def build_hash_store_selection(
|
||||
store_text = str(store_value or "").strip()
|
||||
if not hash_text or not store_text:
|
||||
return None, None
|
||||
args = ["-query", f"hash:{hash_text}", "-store", store_text]
|
||||
args = ["-query", f"hash:{hash_text}", "-instance", store_text]
|
||||
return args, [action_name] + list(args)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user