From 0307a4a996e8f5da103adb407b42f72901007e37 Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:43:16 +0100 Subject: [PATCH] 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. --- .../runners/github/context_gatherer.py | 4 +- .../runners/github/services/__init__.py | 37 ++++++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/backend/runners/github/context_gatherer.py b/apps/backend/runners/github/context_gatherer.py index 535298ee..374f11c6 100644 --- a/apps/backend/runners/github/context_gatherer.py +++ b/apps/backend/runners/github/context_gatherer.py @@ -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 diff --git a/apps/backend/runners/github/services/__init__.py b/apps/backend/runners/github/services/__init__.py index f36e0b51..18228804 100644 --- a/apps/backend/runners/github/services/__init__.py +++ b/apps/backend/runners/github/services/__init__.py @@ -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}")