fix(github): resolve circular import issues in context_gatherer and services (#1026)

- Updated import statements in context_gatherer.py to import safe_print from core.io_utils to avoid circular dependencies with the services package.
- Introduced lazy imports in services/__init__.py to prevent circular import issues, detailing the import chain in comments for clarity.
- Added a lazy import handler to load classes on first access, improving module loading efficiency.
This commit is contained in:
Andy
2026-01-13 14:43:16 +01:00
committed by AndyMik90
parent e7b38d49e0
commit 0307a4a996
2 changed files with 34 additions and 7 deletions
@@ -27,8 +27,10 @@ try:
from .gh_client import GHClient, PRTooLargeError
from .services.io_utils import safe_print
except (ImportError, ValueError, SystemError):
# Import from core.io_utils directly to avoid circular import with services package
# (services/__init__.py imports pr_review_engine which imports context_gatherer)
from core.io_utils import safe_print
from gh_client import GHClient, PRTooLargeError
from services.io_utils import safe_print
# Validation patterns for git refs and paths (defense-in-depth)
# These patterns allow common valid characters while rejecting potentially dangerous ones
@@ -3,14 +3,23 @@ GitHub Orchestrator Services
============================
Service layer for GitHub automation workflows.
NOTE: Uses lazy imports to avoid circular dependency with context_gatherer.py.
The circular import chain was: orchestrator → context_gatherer → services.io_utils
→ services/__init__ → pr_review_engine → context_gatherer (circular!)
"""
from .autofix_processor import AutoFixProcessor
from .batch_processor import BatchProcessor
from .pr_review_engine import PRReviewEngine
from .prompt_manager import PromptManager
from .response_parsers import ResponseParser
from .triage_engine import TriageEngine
from __future__ import annotations
# Lazy import mapping - classes are loaded on first access
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"AutoFixProcessor": (".autofix_processor", "AutoFixProcessor"),
"BatchProcessor": (".batch_processor", "BatchProcessor"),
"PRReviewEngine": (".pr_review_engine", "PRReviewEngine"),
"PromptManager": (".prompt_manager", "PromptManager"),
"ResponseParser": (".response_parsers", "ResponseParser"),
"TriageEngine": (".triage_engine", "TriageEngine"),
}
__all__ = [
"PromptManager",
@@ -20,3 +29,19 @@ __all__ = [
"AutoFixProcessor",
"BatchProcessor",
]
# Cache for lazily loaded modules
_loaded: dict[str, object] = {}
def __getattr__(name: str) -> object:
"""Lazy import handler - loads classes on first access."""
if name in _LAZY_IMPORTS:
if name not in _loaded:
module_name, attr_name = _LAZY_IMPORTS[name]
import importlib
module = importlib.import_module(module_name, __name__)
_loaded[name] = getattr(module, attr_name)
return _loaded[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")