fix(spec): resolve circular import between spec.pipeline and core.client (ACS-302) (#1192)
* fix(spec): resolve circular import between spec.pipeline and core.client Implement lazy imports via __getattr__ to break the circular import chain where spec.pipeline.agent_runner imports core.client, which imports agents.tools_pkg, which imports from spec.validate_pkg. The import chain creates a cycle when spec/__init__.py imports SpecOrchestrator at module level. By deferring these imports via __getattr__, the import chain only executes when these symbols are actually accessed, breaking the cycle. This follows the established pattern used in core/__init__.py, agents/__init__.py, and integrations/graphiti/__init__.py. Refs: ACS-302 * refactor(spec): cache lazy imports in globals() for efficiency Update __getattr__ to cache imported objects in globals() after first access. This ensures subsequent accesses bypass __getattr__ entirely, avoiding redundant import overhead. Also add return type annotation (-> Any) for Python 3.12+ compatibility. Suggested-by: gemini-code-assist Refs: ACS-302 --------- Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
This commit is contained in:
@@ -23,8 +23,17 @@ Usage:
|
||||
)
|
||||
|
||||
success = await orchestrator.run()
|
||||
|
||||
Note:
|
||||
SpecOrchestrator and get_specs_dir are lazy-imported to avoid circular
|
||||
dependencies between spec.pipeline and core.client. The import chain:
|
||||
spec.pipeline.agent_runner imports core.client, which imports
|
||||
agents.tools_pkg, which imports from spec.validate_pkg, causing a cycle
|
||||
when spec/__init__.py imports SpecOrchestrator at module level.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .complexity import (
|
||||
Complexity,
|
||||
ComplexityAnalyzer,
|
||||
@@ -33,7 +42,6 @@ from .complexity import (
|
||||
save_assessment,
|
||||
)
|
||||
from .phases import PhaseExecutor, PhaseResult
|
||||
from .pipeline import SpecOrchestrator, get_specs_dir
|
||||
|
||||
__all__ = [
|
||||
# Main orchestrator
|
||||
@@ -49,3 +57,25 @@ __all__ = [
|
||||
"PhaseExecutor",
|
||||
"PhaseResult",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Lazy imports to avoid circular dependencies with core.client.
|
||||
|
||||
The spec.pipeline module imports from core.client (via agent_runner.py),
|
||||
which imports from agents.tools_pkg, which imports from spec.validate_pkg.
|
||||
This creates a circular dependency when spec/__init__.py imports
|
||||
SpecOrchestrator at module level.
|
||||
|
||||
By deferring these imports via __getattr__, the import chain only
|
||||
executes when these symbols are actually accessed, breaking the cycle.
|
||||
|
||||
Imported objects are cached in globals() to avoid repeated imports.
|
||||
"""
|
||||
if name in ("SpecOrchestrator", "get_specs_dir"):
|
||||
from .pipeline import SpecOrchestrator, get_specs_dir
|
||||
|
||||
# Cache in globals so subsequent accesses bypass __getattr__
|
||||
globals().update(SpecOrchestrator=SpecOrchestrator, get_specs_dir=get_specs_dir)
|
||||
return globals()[name]
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
Reference in New Issue
Block a user