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:
StillKnotKnown
2026-01-16 19:30:17 +02:00
committed by GitHub
parent ae40f819dd
commit 648cf3fc34
+31 -1
View File
@@ -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}")