From d139c69dfa56f72f6c8860fc15f10f9d54a9f5be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:45:06 +0000 Subject: [PATCH 1/3] Initial plan From ad2107e9806bec98cf827fbb976077500ea8fdc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:51:38 +0000 Subject: [PATCH 2/3] Move loader implementations to workflow plugins Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- backend/autometabuilder/__init__.py | 2 +- backend/autometabuilder/app_runner.py | 20 ++++-- backend/autometabuilder/data/__init__.py | 4 +- backend/autometabuilder/loaders/__init__.py | 33 ---------- .../loaders/callable_loader.py | 9 --- backend/autometabuilder/loaders/env_loader.py | 7 --- .../loaders/metadata_loader.py | 38 ------------ .../autometabuilder/loaders/plugin_loader.py | 28 --------- .../autometabuilder/loaders/prompt_loader.py | 14 ----- .../loaders/tool_policy_loader.py | 16 ----- .../loaders/tool_registry_loader.py | 16 ----- .../autometabuilder/loaders/tools_loader.py | 18 ------ backend/autometabuilder/utils.py | 62 +++++++++++++++++++ .../autometabuilder/workflow/plugin_loader.py | 6 +- .../backend_build_tool_map.py | 31 ++++++++-- .../backend_load_env/backend_load_env.py | 4 +- .../backend_load_metadata.py | 41 +++++++++++- .../backend_load_plugins.py | 32 +++++++++- .../backend_load_prompt.py | 14 ++++- .../backend_load_tool_policies.py | 20 +++++- .../backend_load_tool_registry.py | 20 +++++- .../backend_load_tools/backend_load_tools.py | 25 +++++++- .../web_create_translation.py | 2 +- .../web_delete_translation.py | 2 +- .../web_get_ui_messages.py | 2 +- .../web_get_workflow_content.py | 2 +- .../web_list_translations.py | 2 +- .../web_load_translation.py | 2 +- .../web_load_workflow_packages.py | 2 +- .../web_register_blueprint.py | 11 +++- .../web_route_context/web_route_context.py | 2 +- .../web_route_navigation.py | 2 +- .../web_route_translations.py | 2 +- .../web_update_translation.py | 2 +- .../web_write_workflow/web_write_workflow.py | 2 +- backend/tests/test_main.py | 2 +- 36 files changed, 276 insertions(+), 221 deletions(-) delete mode 100644 backend/autometabuilder/loaders/__init__.py delete mode 100644 backend/autometabuilder/loaders/callable_loader.py delete mode 100644 backend/autometabuilder/loaders/env_loader.py delete mode 100644 backend/autometabuilder/loaders/metadata_loader.py delete mode 100644 backend/autometabuilder/loaders/plugin_loader.py delete mode 100644 backend/autometabuilder/loaders/prompt_loader.py delete mode 100644 backend/autometabuilder/loaders/tool_policy_loader.py delete mode 100644 backend/autometabuilder/loaders/tool_registry_loader.py delete mode 100644 backend/autometabuilder/loaders/tools_loader.py create mode 100644 backend/autometabuilder/utils.py diff --git a/backend/autometabuilder/__init__.py b/backend/autometabuilder/__init__.py index 3eb319c..aaef672 100644 --- a/backend/autometabuilder/__init__.py +++ b/backend/autometabuilder/__init__.py @@ -5,7 +5,7 @@ import json import os from pathlib import Path -from .loaders.metadata_loader import load_metadata +from .utils import load_metadata def _load_messages_path(path: Path) -> dict: diff --git a/backend/autometabuilder/app_runner.py b/backend/autometabuilder/app_runner.py index be5c4fc..9fbb6e0 100644 --- a/backend/autometabuilder/app_runner.py +++ b/backend/autometabuilder/app_runner.py @@ -2,8 +2,7 @@ import argparse import logging import os -from .loaders import load_env -from .loaders import load_metadata +from dotenv import load_dotenv from .engine import load_workflow_config, build_workflow_context, build_workflow_engine TRACE_LEVEL = 5 @@ -75,9 +74,22 @@ def run_web_workflow(logger): engine.execute() +def _load_metadata_for_workflow() -> dict: + """Load metadata.json for workflow config.""" + import json + from pathlib import Path + + metadata_path = Path(__file__).resolve().parent / "metadata.json" + if not metadata_path.exists(): + return {} + + with metadata_path.open("r", encoding="utf-8") as f: + return json.load(f) + + def run_app() -> None: """Run the AutoMetabuilder CLI.""" - load_env() + load_dotenv() configure_logging() logger = logging.getLogger("autometabuilder") @@ -98,7 +110,7 @@ def run_app() -> None: } workflow_context = build_workflow_context(context_parts) - metadata = load_metadata() + metadata = _load_metadata_for_workflow() workflow_config = load_workflow_config(metadata) logger.info("Starting workflow: %s", workflow_config.get("name", "Unnamed")) diff --git a/backend/autometabuilder/data/__init__.py b/backend/autometabuilder/data/__init__.py index 45219a6..4e1a697 100644 --- a/backend/autometabuilder/data/__init__.py +++ b/backend/autometabuilder/data/__init__.py @@ -138,5 +138,5 @@ def get_ui_messages(lang): return _run_plugin("web.get_ui_messages", {"lang": lang}) -# Metadata - still using loaders directly -from autometabuilder.loaders.metadata_loader import load_metadata +# Metadata - utility function +from autometabuilder.utils import load_metadata diff --git a/backend/autometabuilder/loaders/__init__.py b/backend/autometabuilder/loaders/__init__.py deleted file mode 100644 index 1c32b3c..0000000 --- a/backend/autometabuilder/loaders/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Loaders module for AutoMetabuilder. - -This module contains various loader utilities: -- callable_loader: Load callables by dotted path -- env_loader: Load environment variables from .env -- metadata_loader: Load metadata.json -- plugin_loader: Load custom tools from plugins directory -- prompt_loader: Load prompt configuration -- tool_policy_loader: Load tool policies from JSON -- tool_registry_loader: Load tool registry entries -- tools_loader: Load tool specs from JSON -""" - -from .callable_loader import load_callable -from .env_loader import load_env -from .metadata_loader import load_metadata -from .plugin_loader import load_plugins -from .prompt_loader import load_prompt_yaml -from .tool_policy_loader import load_tool_policies -from .tool_registry_loader import load_tool_registry -from .tools_loader import load_tools - -__all__ = [ - "load_callable", - "load_env", - "load_metadata", - "load_plugins", - "load_prompt_yaml", - "load_tool_policies", - "load_tool_registry", - "load_tools", -] diff --git a/backend/autometabuilder/loaders/callable_loader.py b/backend/autometabuilder/loaders/callable_loader.py deleted file mode 100644 index 757f244..0000000 --- a/backend/autometabuilder/loaders/callable_loader.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Load a callable by dotted path.""" -import importlib - - -def load_callable(path: str): - """Import and return a callable.""" - module_path, attr = path.rsplit(".", 1) - module = importlib.import_module(module_path) - return getattr(module, attr) diff --git a/backend/autometabuilder/loaders/env_loader.py b/backend/autometabuilder/loaders/env_loader.py deleted file mode 100644 index 1427323..0000000 --- a/backend/autometabuilder/loaders/env_loader.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Load environment variables from .env.""" -from dotenv import load_dotenv - - -def load_env() -> None: - """Load environment variables.""" - load_dotenv() diff --git a/backend/autometabuilder/loaders/metadata_loader.py b/backend/autometabuilder/loaders/metadata_loader.py deleted file mode 100644 index b5d5ddf..0000000 --- a/backend/autometabuilder/loaders/metadata_loader.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Load metadata.json.""" -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -METADATA_PATH = Path(__file__).resolve().parent / "metadata.json" -INCLUDED_SECTIONS = { - "settings_descriptions_path": "settings_descriptions", - "suggestions_path": "suggestions", - "workflow_plugins_path": "workflow_plugins", -} - - -def _read_json(path: Path) -> dict[str, Any]: - if not path.exists(): - return {} - with path.open("r", encoding="utf-8") as f: - return json.load(f) - - -def load_metadata() -> dict[str, Any]: - """Load metadata.json with optional section includes.""" - metadata = _read_json(METADATA_PATH) - base_dir = METADATA_PATH.parent - for path_key, dest_key in INCLUDED_SECTIONS.items(): - include_path = metadata.get(path_key) - if include_path: - resolved_path = base_dir / include_path - if resolved_path.is_dir(): - merged: dict[str, Any] = {} - for file_path in sorted(resolved_path.glob("*.json")): - merged.update(_read_json(file_path)) - metadata[dest_key] = merged - else: - metadata[dest_key] = _read_json(resolved_path) - return metadata diff --git a/backend/autometabuilder/loaders/plugin_loader.py b/backend/autometabuilder/loaders/plugin_loader.py deleted file mode 100644 index 1b924d6..0000000 --- a/backend/autometabuilder/loaders/plugin_loader.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Load custom tools from the plugins directory.""" -import importlib -import inspect -import logging -import os - -logger = logging.getLogger("autometabuilder") - - -def load_plugins(tool_map: dict, tools: list) -> None: - """Load plugin tools and append metadata.""" - plugins_dir = os.path.join(os.path.dirname(__file__), "plugins") - if not os.path.exists(plugins_dir): - return - - for filename in os.listdir(plugins_dir): - if filename.endswith(".py") and filename != "__init__.py": - module_name = f".plugins.{filename[:-3]}" - try: - module = importlib.import_module(module_name, package="autometabuilder") - for name, obj in inspect.getmembers(module): - if inspect.isfunction(obj) and hasattr(obj, "tool_metadata"): - tool_metadata = getattr(obj, "tool_metadata") - tool_map[name] = obj - tools.append(tool_metadata) - logger.info("Loaded plugin tool: %s", name) - except Exception as error: # pylint: disable=broad-exception-caught - logger.error("Failed to load plugin %s: %s", filename, error) diff --git a/backend/autometabuilder/loaders/prompt_loader.py b/backend/autometabuilder/loaders/prompt_loader.py deleted file mode 100644 index 00ca97c..0000000 --- a/backend/autometabuilder/loaders/prompt_loader.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Load prompt configuration.""" -import os -import yaml - -DEFAULT_PROMPT_PATH = "prompt.yml" - - -def load_prompt_yaml() -> dict: - """Load prompt YAML from disk.""" - local_path = os.environ.get("PROMPT_PATH", DEFAULT_PROMPT_PATH) - if os.path.exists(local_path): - with open(local_path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) - raise FileNotFoundError(f"Prompt file not found at {local_path}") diff --git a/backend/autometabuilder/loaders/tool_policy_loader.py b/backend/autometabuilder/loaders/tool_policy_loader.py deleted file mode 100644 index 0204348..0000000 --- a/backend/autometabuilder/loaders/tool_policy_loader.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Load tool policies from JSON.""" -import json -import os - - -def load_tool_policies() -> dict: - """Load tool policies JSON.""" - path = os.path.join(os.path.dirname(__file__), "tool_policies.json") - if not os.path.exists(path): - return {"modifying_tools": []} - try: - with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - except json.JSONDecodeError: - return {"modifying_tools": []} - return data if isinstance(data, dict) else {"modifying_tools": []} diff --git a/backend/autometabuilder/loaders/tool_registry_loader.py b/backend/autometabuilder/loaders/tool_registry_loader.py deleted file mode 100644 index f9481ea..0000000 --- a/backend/autometabuilder/loaders/tool_registry_loader.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Load tool registry entries.""" -import json -import os - - -def load_tool_registry() -> list: - """Load tool registry entries.""" - path = os.path.join(os.path.dirname(__file__), "tool_registry.json") - if not os.path.exists(path): - return [] - try: - with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - except json.JSONDecodeError: - return [] - return data if isinstance(data, list) else [] diff --git a/backend/autometabuilder/loaders/tools_loader.py b/backend/autometabuilder/loaders/tools_loader.py deleted file mode 100644 index b1b4456..0000000 --- a/backend/autometabuilder/loaders/tools_loader.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Load tool specs from JSON.""" -import json -import os - - -def load_tools(metadata: dict) -> list: - """Load tool specs from metadata reference.""" - tools_path = os.path.join(os.path.dirname(__file__), metadata.get("tools_path", "tools.json")) - if os.path.isdir(tools_path): - tools = [] - for filename in sorted(os.listdir(tools_path)): - if not filename.endswith(".json"): - continue - with open(os.path.join(tools_path, filename), "r", encoding="utf-8") as f: - tools.extend(json.load(f)) - return tools - with open(tools_path, "r", encoding="utf-8") as f: - return json.load(f) diff --git a/backend/autometabuilder/utils.py b/backend/autometabuilder/utils.py new file mode 100644 index 0000000..c2cde01 --- /dev/null +++ b/backend/autometabuilder/utils.py @@ -0,0 +1,62 @@ +"""Utility functions for AutoMetabuilder. + +This module provides helper functions that are used across the codebase. +These are pure utility functions that don't contain business logic. +""" +import json +import os +import yaml +from pathlib import Path +from typing import Any + + +def read_json(path: Path) -> dict[str, Any]: + """Read JSON file.""" + if not path.exists(): + return {} + with path.open("r", encoding="utf-8") as f: + return json.load(f) + + +def load_metadata() -> dict[str, Any]: + """Load metadata.json with optional section includes. + + This is a utility function for loading metadata configuration. + """ + included_sections = { + "settings_descriptions_path": "settings_descriptions", + "suggestions_path": "suggestions", + "workflow_plugins_path": "workflow_plugins", + } + + # Locate metadata.json relative to the autometabuilder package root + metadata_path = Path(__file__).resolve().parent / "metadata.json" + metadata = read_json(metadata_path) + base_dir = metadata_path.parent + + for path_key, dest_key in included_sections.items(): + include_path = metadata.get(path_key) + if include_path: + resolved_path = base_dir / include_path + if resolved_path.is_dir(): + merged: dict[str, Any] = {} + for file_path in sorted(resolved_path.glob("*.json")): + merged.update(read_json(file_path)) + metadata[dest_key] = merged + else: + metadata[dest_key] = read_json(resolved_path) + + return metadata + + +def load_prompt_yaml() -> dict: + """Load prompt YAML from disk. + + This is a utility function for loading prompt configuration. + """ + default_prompt_path = "prompt.yml" + local_path = os.environ.get("PROMPT_PATH", default_prompt_path) + if os.path.exists(local_path): + with open(local_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + raise FileNotFoundError(f"Prompt file not found at {local_path}") diff --git a/backend/autometabuilder/workflow/plugin_loader.py b/backend/autometabuilder/workflow/plugin_loader.py index e42eb74..71f7d8a 100644 --- a/backend/autometabuilder/workflow/plugin_loader.py +++ b/backend/autometabuilder/workflow/plugin_loader.py @@ -1,7 +1,9 @@ """Load workflow plugins by dotted path.""" -from ..loaders.callable_loader import load_callable +import importlib def load_plugin_callable(path: str): """Load a workflow plugin callable.""" - return load_callable(path) + module_path, attr = path.rsplit(".", 1) + module = importlib.import_module(module_path) + return getattr(module, attr) diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py b/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py index 6d65a66..1e5dcd8 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py @@ -1,6 +1,29 @@ """Workflow plugin: build tool map.""" -from ....loaders.callable_loader import load_callable -from ....loaders.tool_registry_loader import load_tool_registry +import importlib +import json +import os +from pathlib import Path + + +def _load_callable(path: str): + """Import and return a callable.""" + module_path, attr = path.rsplit(".", 1) + module = importlib.import_module(module_path) + return getattr(module, attr) + + +def _load_tool_registry() -> list: + """Load tool registry entries.""" + # Locate tool_registry.json relative to autometabuilder package root + path = Path(__file__).resolve().parents[4] / "tool_registry.json" + if not os.path.exists(path): + return [] + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + except json.JSONDecodeError: + return [] + return data if isinstance(data, list) else [] def _build_tool_map(gh, registry_entries: list) -> dict: @@ -17,7 +40,7 @@ def _build_tool_map(gh, registry_entries: list) -> dict: continue if provider == "module": path = entry.get("callable") - tool_map[name] = load_callable(path) if path else None + tool_map[name] = _load_callable(path) if path else None continue tool_map[name] = None return tool_map @@ -26,7 +49,7 @@ def _build_tool_map(gh, registry_entries: list) -> dict: def run(runtime, _inputs): """Build tool registry map.""" gh = runtime.context.get("gh") - registry = load_tool_registry() + registry = _load_tool_registry() tool_map = _build_tool_map(gh, registry) # Store in both store (for workflow) and context (for other plugins) runtime.context["tool_map"] = tool_map diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_env/backend_load_env.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_env/backend_load_env.py index 9ac56d6..f651acd 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_env/backend_load_env.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_env/backend_load_env.py @@ -1,8 +1,8 @@ """Workflow plugin: load environment variables.""" -from ....loaders.env_loader import load_env +from dotenv import load_dotenv def run(_runtime, _inputs): """Load environment variables from .env file.""" - load_env() + load_dotenv() return {"result": "Environment loaded"} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py index b7f1770..690c182 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py @@ -1,10 +1,47 @@ """Workflow plugin: load metadata.""" -from ....loaders.metadata_loader import load_metadata +import json +from pathlib import Path +from typing import Any + + +INCLUDED_SECTIONS = { + "settings_descriptions_path": "settings_descriptions", + "suggestions_path": "suggestions", + "workflow_plugins_path": "workflow_plugins", +} + + +def _read_json(path: Path) -> dict[str, Any]: + """Read JSON file.""" + if not path.exists(): + return {} + with path.open("r", encoding="utf-8") as f: + return json.load(f) + + +def _load_metadata() -> dict[str, Any]: + """Load metadata.json with optional section includes.""" + # Locate metadata.json relative to the autometabuilder package root + metadata_path = Path(__file__).resolve().parents[4] / "metadata.json" + metadata = _read_json(metadata_path) + base_dir = metadata_path.parent + for path_key, dest_key in INCLUDED_SECTIONS.items(): + include_path = metadata.get(path_key) + if include_path: + resolved_path = base_dir / include_path + if resolved_path.is_dir(): + merged: dict[str, Any] = {} + for file_path in sorted(resolved_path.glob("*.json")): + merged.update(_read_json(file_path)) + metadata[dest_key] = merged + else: + metadata[dest_key] = _read_json(resolved_path) + return metadata def run(runtime, _inputs): """Load metadata.json.""" - metadata = load_metadata() + metadata = _load_metadata() # Store in both store (for workflow) and context (for other plugins) runtime.context["metadata"] = metadata return {"result": metadata} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py index 99812ff..80c1030 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py @@ -1,10 +1,38 @@ """Workflow plugin: load and register plugins.""" -from ....loaders.plugin_loader import load_plugins +import importlib +import inspect +import logging +import os +from pathlib import Path + +logger = logging.getLogger("autometabuilder") + + +def _load_plugins(tool_map: dict, tools: list) -> None: + """Load plugin tools and append metadata.""" + # Locate plugins directory relative to autometabuilder package root + plugins_dir = Path(__file__).resolve().parents[4] / "plugins" + if not os.path.exists(plugins_dir): + return + + for filename in os.listdir(plugins_dir): + if filename.endswith(".py") and filename != "__init__.py": + module_name = f".plugins.{filename[:-3]}" + try: + module = importlib.import_module(module_name, package="autometabuilder") + for name, obj in inspect.getmembers(module): + if inspect.isfunction(obj) and hasattr(obj, "tool_metadata"): + tool_metadata = getattr(obj, "tool_metadata") + tool_map[name] = obj + tools.append(tool_metadata) + logger.info("Loaded plugin tool: %s", name) + except Exception as error: # pylint: disable=broad-exception-caught + logger.error("Failed to load plugin %s: %s", filename, error) def run(runtime, _inputs): """Load and register plugins.""" tool_map = runtime.context.get("tool_map", {}) tools = runtime.context.get("tools", []) - load_plugins(tool_map, tools) + _load_plugins(tool_map, tools) return {"result": True} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_prompt/backend_load_prompt.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_prompt/backend_load_prompt.py index b45ced6..5fe83c0 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_prompt/backend_load_prompt.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_prompt/backend_load_prompt.py @@ -1,10 +1,20 @@ """Workflow plugin: load prompt configuration.""" import os -from ....loaders.prompt_loader import load_prompt_yaml +import yaml +DEFAULT_PROMPT_PATH = "prompt.yml" DEFAULT_MODEL = "openai/gpt-4o" +def _load_prompt_yaml() -> dict: + """Load prompt YAML from disk.""" + local_path = os.environ.get("PROMPT_PATH", DEFAULT_PROMPT_PATH) + if os.path.exists(local_path): + with open(local_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + raise FileNotFoundError(f"Prompt file not found at {local_path}") + + def _resolve_model_name(prompt: dict) -> str: """Resolve model name from env or prompt.""" return os.environ.get("LLM_MODEL", prompt.get("model", DEFAULT_MODEL)) @@ -12,7 +22,7 @@ def _resolve_model_name(prompt: dict) -> str: def run(runtime, _inputs): """Load prompt.yml.""" - prompt = load_prompt_yaml() + prompt = _load_prompt_yaml() # Store in both store (for workflow) and context (for other plugins) runtime.context["prompt"] = prompt # Update model_name based on loaded prompt diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py index ba2a59f..61a018f 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py @@ -1,10 +1,26 @@ """Workflow plugin: load tool policies.""" -from ....loaders.tool_policy_loader import load_tool_policies +import json +import os +from pathlib import Path + + +def _load_tool_policies() -> dict: + """Load tool policies JSON.""" + # Locate tool_policies.json relative to autometabuilder package root + path = Path(__file__).resolve().parents[4] / "tool_policies.json" + if not os.path.exists(path): + return {"modifying_tools": []} + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + except json.JSONDecodeError: + return {"modifying_tools": []} + return data if isinstance(data, dict) else {"modifying_tools": []} def run(runtime, _inputs): """Load tool_policies.json.""" - tool_policies = load_tool_policies() + tool_policies = _load_tool_policies() # Store in both store (for workflow) and context (for other plugins) runtime.context["tool_policies"] = tool_policies return {"result": tool_policies} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py index c0bbf04..9652059 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py @@ -1,10 +1,26 @@ """Workflow plugin: load tool registry.""" -from ....loaders.tool_registry_loader import load_tool_registry +import json +import os +from pathlib import Path + + +def _load_tool_registry() -> list: + """Load tool registry entries.""" + # Locate tool_registry.json relative to autometabuilder package root + path = Path(__file__).resolve().parents[4] / "tool_registry.json" + if not os.path.exists(path): + return [] + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + except json.JSONDecodeError: + return [] + return data if isinstance(data, list) else [] def run(runtime, _inputs): """Load tool registry entries.""" - tool_registry = load_tool_registry() + tool_registry = _load_tool_registry() # Store in context for other plugins runtime.context["tool_registry"] = tool_registry return {"result": tool_registry} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py index 8ec19af..0f98365 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py @@ -1,11 +1,32 @@ """Workflow plugin: load tools.""" -from ....loaders.tools_loader import load_tools +import json +import os +from pathlib import Path + + +def _load_tools(metadata: dict) -> list: + """Load tool specs from metadata reference.""" + # Locate tools relative to autometabuilder package root + base_dir = Path(__file__).resolve().parents[4] + tools_path = base_dir / metadata.get("tools_path", "tools.json") + + if tools_path.is_dir(): + tools = [] + for filename in sorted(os.listdir(tools_path)): + if not filename.endswith(".json"): + continue + with open(tools_path / filename, "r", encoding="utf-8") as f: + tools.extend(json.load(f)) + return tools + + with open(tools_path, "r", encoding="utf-8") as f: + return json.load(f) def run(runtime, _inputs): """Load tool definitions.""" metadata = runtime.context.get("metadata", {}) - tools = load_tools(metadata) + tools = _load_tools(metadata) # Store in both store (for workflow) and context (for other plugins) runtime.context["tools"] = tools return {"result": tools} diff --git a/backend/autometabuilder/workflow/plugins/web/web_create_translation/web_create_translation.py b/backend/autometabuilder/workflow/plugins/web/web_create_translation/web_create_translation.py index 24a0e9c..abfe2ab 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_create_translation/web_create_translation.py +++ b/backend/autometabuilder/workflow/plugins/web/web_create_translation/web_create_translation.py @@ -2,7 +2,7 @@ import json import shutil from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_delete_translation/web_delete_translation.py b/backend/autometabuilder/workflow/plugins/web/web_delete_translation/web_delete_translation.py index 1b999e9..c619f08 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_delete_translation/web_delete_translation.py +++ b/backend/autometabuilder/workflow/plugins/web/web_delete_translation/web_delete_translation.py @@ -2,7 +2,7 @@ import json import shutil from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_get_ui_messages/web_get_ui_messages.py b/backend/autometabuilder/workflow/plugins/web/web_get_ui_messages/web_get_ui_messages.py index 796e33e..8f87b3a 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_get_ui_messages/web_get_ui_messages.py +++ b/backend/autometabuilder/workflow/plugins/web/web_get_ui_messages/web_get_ui_messages.py @@ -1,7 +1,7 @@ """Workflow plugin: get UI messages.""" import json from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_get_workflow_content/web_get_workflow_content.py b/backend/autometabuilder/workflow/plugins/web/web_get_workflow_content/web_get_workflow_content.py index cb329e6..04420f9 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_get_workflow_content/web_get_workflow_content.py +++ b/backend/autometabuilder/workflow/plugins/web/web_get_workflow_content/web_get_workflow_content.py @@ -1,6 +1,6 @@ """Workflow plugin: get workflow content.""" from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, _inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_list_translations/web_list_translations.py b/backend/autometabuilder/workflow/plugins/web/web_list_translations/web_list_translations.py index 50deea3..e563a0e 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_list_translations/web_list_translations.py +++ b/backend/autometabuilder/workflow/plugins/web/web_list_translations/web_list_translations.py @@ -1,7 +1,7 @@ """Workflow plugin: list translations.""" import json from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, _inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_load_translation/web_load_translation.py b/backend/autometabuilder/workflow/plugins/web/web_load_translation/web_load_translation.py index 513267d..bc72f73 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_load_translation/web_load_translation.py +++ b/backend/autometabuilder/workflow/plugins/web/web_load_translation/web_load_translation.py @@ -1,7 +1,7 @@ """Workflow plugin: load translation.""" import json from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_load_workflow_packages/web_load_workflow_packages.py b/backend/autometabuilder/workflow/plugins/web/web_load_workflow_packages/web_load_workflow_packages.py index 33fbb2b..a46355c 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_load_workflow_packages/web_load_workflow_packages.py +++ b/backend/autometabuilder/workflow/plugins/web/web_load_workflow_packages/web_load_workflow_packages.py @@ -2,7 +2,7 @@ import json import logging from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata logger = logging.getLogger(__name__) diff --git a/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py b/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py index 189e8a2..7b8e909 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py +++ b/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py @@ -1,4 +1,12 @@ """Workflow plugin: register Flask blueprint.""" +import importlib + + +def _load_callable(path: str): + """Import and return a callable.""" + module_path, attr = path.rsplit(".", 1) + module = importlib.import_module(module_path) + return getattr(module, attr) def run(runtime, inputs): @@ -25,9 +33,8 @@ def run(runtime, inputs): if not blueprint_path: return {"error": "blueprint or blueprint_path is required"} - from ....loaders.callable_loader import load_callable try: - blueprint = load_callable(blueprint_path) + blueprint = _load_callable(blueprint_path) except Exception as e: return {"error": f"Failed to load blueprint: {str(e)}"} diff --git a/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py b/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py index 920a154..f9ec189 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py +++ b/backend/autometabuilder/workflow/plugins/web/web_route_context/web_route_context.py @@ -1,7 +1,7 @@ """Workflow plugin: context API routes blueprint.""" import os from flask import Blueprint, jsonify -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata from autometabuilder.workflow.plugin_loader import load_plugin_callable from autometabuilder.roadmap_utils import is_mvp_reached diff --git a/backend/autometabuilder/workflow/plugins/web/web_route_navigation/web_route_navigation.py b/backend/autometabuilder/workflow/plugins/web/web_route_navigation/web_route_navigation.py index 1a387e8..cfc1aec 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_route_navigation/web_route_navigation.py +++ b/backend/autometabuilder/workflow/plugins/web/web_route_navigation/web_route_navigation.py @@ -1,6 +1,6 @@ """Workflow plugin: navigation API routes blueprint.""" from flask import Blueprint, jsonify -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata from autometabuilder.workflow.workflow_graph import build_workflow_graph diff --git a/backend/autometabuilder/workflow/plugins/web/web_route_translations/web_route_translations.py b/backend/autometabuilder/workflow/plugins/web/web_route_translations/web_route_translations.py index ce925b1..c63e0fd 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_route_translations/web_route_translations.py +++ b/backend/autometabuilder/workflow/plugins/web/web_route_translations/web_route_translations.py @@ -1,6 +1,6 @@ """Workflow plugin: translation API routes blueprint.""" from flask import Blueprint, jsonify, request -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(runtime, _inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_update_translation/web_update_translation.py b/backend/autometabuilder/workflow/plugins/web/web_update_translation/web_update_translation.py index ef50d5f..50eaa27 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_update_translation/web_update_translation.py +++ b/backend/autometabuilder/workflow/plugins/web/web_update_translation/web_update_translation.py @@ -1,7 +1,7 @@ """Workflow plugin: update translation.""" import json from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, inputs): diff --git a/backend/autometabuilder/workflow/plugins/web/web_write_workflow/web_write_workflow.py b/backend/autometabuilder/workflow/plugins/web/web_write_workflow/web_write_workflow.py index 6372efb..805a741 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_write_workflow/web_write_workflow.py +++ b/backend/autometabuilder/workflow/plugins/web/web_write_workflow/web_write_workflow.py @@ -1,6 +1,6 @@ """Workflow plugin: write workflow.""" from pathlib import Path -from autometabuilder.loaders.metadata_loader import load_metadata +from autometabuilder.utils import load_metadata def run(_runtime, inputs): diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index f197f8b..756c58a 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -1,7 +1,7 @@ import os import unittest -from autometabuilder.loaders.prompt_loader import load_prompt_yaml +from autometabuilder.utils import load_prompt_yaml class TestMain(unittest.TestCase): def test_load_prompt_yaml(self): From 053595f5667fca696bc9578b46d3475155337dfc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:57:57 +0000 Subject: [PATCH 3/3] Refactor: consolidate utilities and fix code duplication Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- backend/autometabuilder/utils.py | 43 ++++++++++++++++++- .../autometabuilder/workflow/plugin_loader.py | 6 +-- .../backend_build_tool_map.py | 30 ++----------- .../backend_load_metadata.py | 41 +----------------- .../backend_load_plugins.py | 6 +-- .../backend_load_tool_policies.py | 6 +-- .../backend_load_tool_registry.py | 20 +-------- .../backend_load_tools/backend_load_tools.py | 4 +- .../web_register_blueprint.py | 11 +---- 9 files changed, 60 insertions(+), 107 deletions(-) diff --git a/backend/autometabuilder/utils.py b/backend/autometabuilder/utils.py index c2cde01..891ff78 100644 --- a/backend/autometabuilder/utils.py +++ b/backend/autometabuilder/utils.py @@ -3,6 +3,7 @@ This module provides helper functions that are used across the codebase. These are pure utility functions that don't contain business logic. """ +import importlib import json import os import yaml @@ -10,6 +11,14 @@ from pathlib import Path from typing import Any +def get_package_root() -> Path: + """Get the AutoMetabuilder package root directory. + + Returns the absolute path to the autometabuilder package root. + """ + return Path(__file__).resolve().parent + + def read_json(path: Path) -> dict[str, Any]: """Read JSON file.""" if not path.exists(): @@ -18,6 +27,20 @@ def read_json(path: Path) -> dict[str, Any]: return json.load(f) +def load_callable(path: str): + """Import and return a callable by dotted path. + + Args: + path: Dotted path to callable (e.g., 'module.submodule.function') + + Returns: + The callable object + """ + module_path, attr = path.rsplit(".", 1) + module = importlib.import_module(module_path) + return getattr(module, attr) + + def load_metadata() -> dict[str, Any]: """Load metadata.json with optional section includes. @@ -29,8 +52,8 @@ def load_metadata() -> dict[str, Any]: "workflow_plugins_path": "workflow_plugins", } - # Locate metadata.json relative to the autometabuilder package root - metadata_path = Path(__file__).resolve().parent / "metadata.json" + # Locate metadata.json in package root + metadata_path = get_package_root() / "metadata.json" metadata = read_json(metadata_path) base_dir = metadata_path.parent @@ -60,3 +83,19 @@ def load_prompt_yaml() -> dict: with open(local_path, "r", encoding="utf-8") as f: return yaml.safe_load(f) raise FileNotFoundError(f"Prompt file not found at {local_path}") + + +def load_tool_registry() -> list: + """Load tool registry entries from tool_registry.json. + + This is a utility function for loading tool registry configuration. + """ + path = get_package_root() / "tool_registry.json" + if not os.path.exists(path): + return [] + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + except json.JSONDecodeError: + return [] + return data if isinstance(data, list) else [] diff --git a/backend/autometabuilder/workflow/plugin_loader.py b/backend/autometabuilder/workflow/plugin_loader.py index 71f7d8a..f0cfd0e 100644 --- a/backend/autometabuilder/workflow/plugin_loader.py +++ b/backend/autometabuilder/workflow/plugin_loader.py @@ -1,9 +1,7 @@ """Load workflow plugins by dotted path.""" -import importlib +from ..utils import load_callable def load_plugin_callable(path: str): """Load a workflow plugin callable.""" - module_path, attr = path.rsplit(".", 1) - module = importlib.import_module(module_path) - return getattr(module, attr) + return load_callable(path) diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py b/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py index 1e5dcd8..c9e85cd 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_build_tool_map/backend_build_tool_map.py @@ -1,29 +1,5 @@ """Workflow plugin: build tool map.""" -import importlib -import json -import os -from pathlib import Path - - -def _load_callable(path: str): - """Import and return a callable.""" - module_path, attr = path.rsplit(".", 1) - module = importlib.import_module(module_path) - return getattr(module, attr) - - -def _load_tool_registry() -> list: - """Load tool registry entries.""" - # Locate tool_registry.json relative to autometabuilder package root - path = Path(__file__).resolve().parents[4] / "tool_registry.json" - if not os.path.exists(path): - return [] - try: - with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - except json.JSONDecodeError: - return [] - return data if isinstance(data, list) else [] +from .....utils import load_callable, load_tool_registry def _build_tool_map(gh, registry_entries: list) -> dict: @@ -40,7 +16,7 @@ def _build_tool_map(gh, registry_entries: list) -> dict: continue if provider == "module": path = entry.get("callable") - tool_map[name] = _load_callable(path) if path else None + tool_map[name] = load_callable(path) if path else None continue tool_map[name] = None return tool_map @@ -49,7 +25,7 @@ def _build_tool_map(gh, registry_entries: list) -> dict: def run(runtime, _inputs): """Build tool registry map.""" gh = runtime.context.get("gh") - registry = _load_tool_registry() + registry = load_tool_registry() tool_map = _build_tool_map(gh, registry) # Store in both store (for workflow) and context (for other plugins) runtime.context["tool_map"] = tool_map diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py index 690c182..5e80934 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_metadata/backend_load_metadata.py @@ -1,47 +1,10 @@ """Workflow plugin: load metadata.""" -import json -from pathlib import Path -from typing import Any - - -INCLUDED_SECTIONS = { - "settings_descriptions_path": "settings_descriptions", - "suggestions_path": "suggestions", - "workflow_plugins_path": "workflow_plugins", -} - - -def _read_json(path: Path) -> dict[str, Any]: - """Read JSON file.""" - if not path.exists(): - return {} - with path.open("r", encoding="utf-8") as f: - return json.load(f) - - -def _load_metadata() -> dict[str, Any]: - """Load metadata.json with optional section includes.""" - # Locate metadata.json relative to the autometabuilder package root - metadata_path = Path(__file__).resolve().parents[4] / "metadata.json" - metadata = _read_json(metadata_path) - base_dir = metadata_path.parent - for path_key, dest_key in INCLUDED_SECTIONS.items(): - include_path = metadata.get(path_key) - if include_path: - resolved_path = base_dir / include_path - if resolved_path.is_dir(): - merged: dict[str, Any] = {} - for file_path in sorted(resolved_path.glob("*.json")): - merged.update(_read_json(file_path)) - metadata[dest_key] = merged - else: - metadata[dest_key] = _read_json(resolved_path) - return metadata +from .....utils import load_metadata as util_load_metadata def run(runtime, _inputs): """Load metadata.json.""" - metadata = _load_metadata() + metadata = util_load_metadata() # Store in both store (for workflow) and context (for other plugins) runtime.context["metadata"] = metadata return {"result": metadata} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py index 80c1030..18bd7c4 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_plugins/backend_load_plugins.py @@ -3,15 +3,15 @@ import importlib import inspect import logging import os -from pathlib import Path +from .....utils import get_package_root logger = logging.getLogger("autometabuilder") def _load_plugins(tool_map: dict, tools: list) -> None: """Load plugin tools and append metadata.""" - # Locate plugins directory relative to autometabuilder package root - plugins_dir = Path(__file__).resolve().parents[4] / "plugins" + # Locate plugins directory in package root + plugins_dir = get_package_root() / "plugins" if not os.path.exists(plugins_dir): return diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py index 61a018f..6997e38 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_policies/backend_load_tool_policies.py @@ -1,13 +1,13 @@ """Workflow plugin: load tool policies.""" import json import os -from pathlib import Path +from .....utils import get_package_root def _load_tool_policies() -> dict: """Load tool policies JSON.""" - # Locate tool_policies.json relative to autometabuilder package root - path = Path(__file__).resolve().parents[4] / "tool_policies.json" + # Locate tool_policies.json in package root + path = get_package_root() / "tool_policies.json" if not os.path.exists(path): return {"modifying_tools": []} try: diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py index 9652059..68641e1 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_tool_registry/backend_load_tool_registry.py @@ -1,26 +1,10 @@ """Workflow plugin: load tool registry.""" -import json -import os -from pathlib import Path - - -def _load_tool_registry() -> list: - """Load tool registry entries.""" - # Locate tool_registry.json relative to autometabuilder package root - path = Path(__file__).resolve().parents[4] / "tool_registry.json" - if not os.path.exists(path): - return [] - try: - with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - except json.JSONDecodeError: - return [] - return data if isinstance(data, list) else [] +from .....utils import load_tool_registry as util_load_tool_registry def run(runtime, _inputs): """Load tool registry entries.""" - tool_registry = _load_tool_registry() + tool_registry = util_load_tool_registry() # Store in context for other plugins runtime.context["tool_registry"] = tool_registry return {"result": tool_registry} diff --git a/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py b/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py index 0f98365..57259d9 100644 --- a/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py +++ b/backend/autometabuilder/workflow/plugins/backend/backend_load_tools/backend_load_tools.py @@ -1,13 +1,13 @@ """Workflow plugin: load tools.""" import json import os -from pathlib import Path +from .....utils import get_package_root def _load_tools(metadata: dict) -> list: """Load tool specs from metadata reference.""" # Locate tools relative to autometabuilder package root - base_dir = Path(__file__).resolve().parents[4] + base_dir = get_package_root() tools_path = base_dir / metadata.get("tools_path", "tools.json") if tools_path.is_dir(): diff --git a/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py b/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py index 7b8e909..e3ab397 100644 --- a/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py +++ b/backend/autometabuilder/workflow/plugins/web/web_register_blueprint/web_register_blueprint.py @@ -1,12 +1,5 @@ """Workflow plugin: register Flask blueprint.""" -import importlib - - -def _load_callable(path: str): - """Import and return a callable.""" - module_path, attr = path.rsplit(".", 1) - module = importlib.import_module(module_path) - return getattr(module, attr) +from .....utils import load_callable def run(runtime, inputs): @@ -34,7 +27,7 @@ def run(runtime, inputs): return {"error": "blueprint or blueprint_path is required"} try: - blueprint = _load_callable(blueprint_path) + blueprint = load_callable(blueprint_path) except Exception as e: return {"error": f"Failed to load blueprint: {str(e)}"}