Repository navigation
fix: US module namespace collision — cores/ import resolution failure - #227
Conversation
When running us_stock_analysis_orchestrator.py, Python caches the main project's `cores` package first (triggered by early imports like TelegramConfig). All subsequent `from cores.xxx` imports then resolve against the main `cores/` directory, even when the target module only exists in `prism-us/cores/`. This caused two ImportErrors: 1. `from cores.data_prefetch import prefetch_us_macro_intelligence_data` → main cores/data_prefetch.py does not have this function 2. `from cores.us_surge_detector import ...` → main cores/ does not have us_surge_detector.py at all Fix: use file-based imports (importlib.util.spec_from_file_location) to bypass Python's module cache and load directly from prism-us/cores/. This is the same pattern already used by `_import_from_main_cores()` for the reverse direction. Related: dragon1086#220 (same class of bug in us_pending_order_batch.py) Co-Authored-By: Claude Opus 4.6 <[email protected]>
Same class of bug as the previous commit — `from cores.xxx` resolves against main cores/ instead of prism-us/cores/: 1. `create_us_macro_intelligence_agent` (line 270): only exists in prism-us/cores/agents/macro_intelligence_agent.py 2. `analyze_us_stock` (line 459): only exists in prism-us/cores/us_analysis.py Both now use `_import_from_us_cores()` for file-based import. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Hey, thanks for the PR! One thing I wanted to check — we've been running the exact same setup on our production server: and we haven't seen this ImportError come up at all. Looking at the code, the sys.path.insert(0, str(PROJECT_ROOT)) # project root
sys.path.insert(0, str(PRISM_US_DIR)) # prism-us/ (ends up at index 0)Since Could you share a bit more about the environment where you hit this? (Python version, exact command you ran, or whether it was from an IDE, etc.) Also a minor concern with the
If you can share the repro steps, happy to figure out the right fix together! (혹시 한국분이시면 편하게 한국어로 답변 주셔도 됩니다!) |
|
안녕하세요! 한국어 답변 감사합니다 😄 한국어로 쓸게요. 재현 환경
Root Cause리뷰어분 말씀이 맞습니다. 문제는 if not args.no_proxy and os.getenv("PRISM_OPENAI_AUTH_MODE") == "chatgpt_oauth":
sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) # PROJECT_ROOT
from cores.chatgpt_proxy import inject_env, start_proxy # ← 여기이
production에서 안 나는 이유는 API 키 모드를 사용하면 line 1189 조건이 false가 되어 더 나은 수정 방향리뷰어분의 importlib.util side-effect 우려(이중 로딩, 내부 import 불일치, 디버깅 어려움)에 동의합니다. 현재 PR은 # Before (sys.modules['cores']를 메인으로 캐싱)
from cores.chatgpt_proxy import inject_env, start_proxy
# After (importlib.util로 직접 로드, sys.modules 오염 없음)
_proxy_mod = _import_from_main_cores("cores_chatgpt_proxy", "cores/chatgpt_proxy/__init__.py")
inject_env = _proxy_mod.inject_env
start_proxy = _proxy_mod.start_proxy이렇게 하면:
원하시면 이 방향으로 PR을 업데이트하겠습니다! |
|
와 분석 정말 깔끔하시네요! 👍 root cause 정확히 짚어주셨습니다.
제안해주신 다만 제가 코드를 좀 더 살펴보다가 보완이 필요한 부분 3가지를 발견해서 공유드려요. 이 부분까지 반영하시면 훨씬 견고한 수정이 될 것 같습니다. 보완 사항 3가지1.
|
| 항목 | 내용 |
|---|---|
| 목표 | chatgpt_oauth 모드에서 sys.modules['cores'] 오염으로 인한 ImportError 해결 |
| 범위 | prism-us/us_stock_analysis_orchestrator.py 단일 파일 수정 |
| 비범위 | us_trigger_batch.py 등 다른 파일의 _import_from_us_cores 적용 (불필요) |
| 제약 | 기존 API 키 모드 동작에 영향 없어야 함 |
수정 계획
Step 1: _import_from_main_cores 헬퍼를 패키지 로딩 지원하도록 확장 (또는 별도 헬퍼 추가)
def _import_package_from_main_cores(package_name: str, relative_dir: str):
"""
Import a package (with relative imports) from main project cores/.
Unlike _import_from_main_cores, this handles __init__.py with relative imports.
"""
import importlib.util, types
pkg_dir = PROJECT_ROOT / relative_dir
# cores 패키지가 sys.modules에 없으면 synthetic parent 등록
if "cores" not in sys.modules:
cores_pkg = types.ModuleType("cores")
cores_pkg.__path__ = [str(PROJECT_ROOT / "cores")]
cores_pkg.__package__ = "cores"
sys.modules["cores"] = cores_pkg
spec = importlib.util.spec_from_file_location(
package_name,
pkg_dir / "__init__.py",
submodule_search_locations=[str(pkg_dir)]
)
module = importlib.util.module_from_spec(spec)
module.__package__ = package_name
sys.modules[package_name] = module
spec.loader.exec_module(module)
return module
⚠️ 주의: 이 방식은sys.modules["cores"]에 synthetic 모듈을 넣으므로,__path__가PROJECT_ROOT/cores/를 가리킵니다. 이러면 이후from cores.data_prefetch가 메인 cores에서 해석될 수 있어요.그래서 더 안전한 대안은 아래처럼 save/restore 패턴입니다:
def _import_proxy_safe():
"""chatgpt_proxy를 로드하되 sys.modules['cores'] 오염 방지"""
saved = sys.modules.get("cores")
saved_path = list(sys.path)
try:
sys.path.insert(0, str(PROJECT_ROOT))
# 캐시된 cores가 있으면 제거해서 fresh resolve
if "cores" in sys.modules:
del sys.modules["cores"]
import cores.chatgpt_proxy as proxy_mod
return proxy_mod
finally:
# 원래 상태 복원
sys.path[:] = saved_path
if saved is not None:
sys.modules["cores"] = saved
elif "cores" in sys.modules:
del sys.modules["cores"]Step 2: main() 함수의 chatgpt_oauth 블록 수정
# Before (현재 코드 - 3곳에서 from cores.chatgpt_proxy import ...)
if not args.no_proxy and os.getenv("PRISM_OPENAI_AUTH_MODE") == "chatgpt_oauth":
try:
sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) # ← 제거
from cores.chatgpt_proxy import inject_env, start_proxy # ← 변경
...
# After
if not args.no_proxy and os.getenv("PRISM_OPENAI_AUTH_MODE") == "chatgpt_oauth":
try:
_proxy_mod = _import_proxy_safe() # 한 번만 로드
inject_env = _proxy_mod.inject_env
start_proxy = _proxy_mod.start_proxy
clear_env = _proxy_mod.clear_env
stop_proxy = _proxy_mod.stop_proxy
inject_env()
proxy_started = await start_proxy()
if not proxy_started:
logger.warning("ChatGPT OAuth proxy failed to start, falling back to standard API")
clear_env()
except Exception as e:
logger.warning("ChatGPT OAuth proxy setup error: %s, falling back to standard API", e)그리고 line 1196-1202의 stop_proxy 블록도:
# Before
if proxy_started:
try:
sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) # ← 제거
from cores.chatgpt_proxy import stop_proxy # ← 제거
await stop_proxy()
# After (위에서 이미 stop_proxy를 꺼내뒀으므로)
if proxy_started:
try:
await stop_proxy() # 위에서 캐싱한 같은 모듈 인스턴스
except Exception:
passStep 3: 원래 PR에서 추가한 _import_from_us_cores() 헬퍼와 관련 4곳 변경 → 전부 되돌리기 (불필요)
변경 요약
| 파일 | 변경 내용 |
|---|---|
us_stock_analysis_orchestrator.py |
_import_proxy_safe() 헬퍼 추가 (약 15줄) |
us_stock_analysis_orchestrator.py |
line 1173 sys.path.insert 제거 |
us_stock_analysis_orchestrator.py |
line 1174, 1179 → _proxy_mod에서 추출 |
us_stock_analysis_orchestrator.py |
line 1198-1199 sys.path.insert + import 제거 |
us_stock_analysis_orchestrator.py |
_import_from_us_cores 헬퍼 + 4곳 수정 되돌리기 |
us_trigger_batch.py |
원래 PR 변경 되돌리기 (수정 불필요) |
E2E 테스트 방법
수정 후 아래 순서로 검증하시면 됩니다.
테스트 1: chatgpt_oauth 모드 (버그 재현 → 수정 확인)
# 수정 전 (현재 PR 상태) — ImportError 재현되는지 확인
PRISM_OPENAI_AUTH_MODE=chatgpt_oauth python3 prism-us/us_stock_analysis_orchestrator.py --mode morning
# 수정 후 — ImportError 없이 정상 진행되는지 확인
PRISM_OPENAI_AUTH_MODE=chatgpt_oauth python3 prism-us/us_stock_analysis_orchestrator.py --mode morningOAuth 토큰이 만료된 상태라면 프록시 시작이 실패하겠지만, 핵심은 ImportError가 안 나고 graceful fallback 되는지입니다. 로그에 아래처럼 나오면 성공:
WARNING - ChatGPT OAuth proxy failed to start, falling back to standard API
(이후 일반 API 키로 분석이 계속 진행되면 OK)
테스트 2: 일반 API 키 모드 (기존 동작 regression 없는지)
# chatgpt_oauth 없이 실행 — 기존대로 잘 돌아야 함
python3 prism-us/us_stock_analysis_orchestrator.py --mode morning테스트 3: sys.modules 오염 확인 (디버깅용)
import 직후에 간단한 확인 코드를 임시로 넣어서 체크할 수 있어요:
# _import_proxy_safe() 호출 후에 임시로 추가
print(f"cores in sys.modules: {'cores' in sys.modules}")
print(f"sys.path[0]: {sys.path[0]}")
# cores가 sys.modules에 없거나, 있더라도 prism-us/cores/ 를 가리키면 OK
if "cores" in sys.modules:
print(f"cores.__path__: {sys.modules['cores'].__path__}")테스트 4: 프록시 종료 확인 (포트 누수 체크)
# 실행 전
lsof -i :18741 # 아무것도 없어야 함
# 실행 후 (프로세스 종료 후)
lsof -i :18741 # 역시 아무것도 없어야 함 — 포트 정리 확인검증 체크리스트
-
chatgpt_oauth모드: ImportError 없이 실행 (또는 graceful fallback) - 일반 API 키 모드: regression 없음
-
sys.modules['cores']가prism-us/cores/를 가리키거나 미등록 상태 -
sys.path순서가 원래대로 유지 (PRISM_US_DIR이PROJECT_ROOT보다 앞) - 프록시 시작/종료 시 포트 18741 정상 해제
-
us_trigger_batch.py수정 없이 정상 동작
수정하시면서 궁금한 점 있으시면 편하게 물어봐주세요!
Address maintainer review feedback on PR dragon1086#227: 1. chatgpt_proxy is a package with relative imports, so importlib.util.spec_from_file_location fails. Use standard import with sys.modules/sys.path save/restore instead. 2. Load proxy module once to avoid singleton issues — module-level state (_runner, _site) must be shared across start/stop calls. 3. Remove unnecessary sys.path.insert(0, PROJECT_ROOT) lines. 4. Revert _import_from_us_cores helper and all 3 call sites — root cause fix (preventing sys.modules pollution) makes them unnecessary. 5. Revert us_trigger_batch.py changes — not affected by this bug. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
피드백 감사합니다! 세 가지 보완 사항 모두 반영해서 업데이트했습니다. 변경 내용
def _import_proxy_safe():
saved_cores = sys.modules.get("cores")
saved_path = list(sys.path)
try:
sys.path.insert(0, str(PROJECT_ROOT))
if "cores" in sys.modules:
del sys.modules["cores"]
import cores.chatgpt_proxy as proxy_mod
return proxy_mod
finally:
sys.path[:] = saved_path
for key in [k for k in sys.modules if k.startswith("cores.chatgpt_proxy")]:
del sys.modules[key]
if saved_cores is not None:
sys.modules["cores"] = saved_cores
elif "cores" in sys.modules:
del sys.modules["cores"]반영 사항:
추가 반영:
검증 결과:
Summary for English readers: All 3 issues have been addressed in this update. Issue 1 — Relative import failure: Issue 2 — Module singleton problem: The proxy module has module-level state ( Issue 3 — Leftover Additional changes:
Verification (macOS, Python 3.14):
|
dragon1086
left a comment
There was a problem hiding this comment.
@lifrary 로컬 E2E테스트 완료했습니다. 최초 기여에 진심으로 감사드립니다. 운영서버에 반영하여 테스트 후 이상 생기면 제가 자체적으로 수정하겠습니다. 추후 릴리즈노트에 해당 PR언급하도록 하겠습니다. 감사합니다.
Address maintainer review feedback on PR dragon1086#227: 1. chatgpt_proxy is a package with relative imports, so importlib.util.spec_from_file_location fails. Use standard import with sys.modules/sys.path save/restore instead. 2. Load proxy module once to avoid singleton issues — module-level state (_runner, _site) must be shared across start/stop calls. 3. Remove unnecessary sys.path.insert(0, PROJECT_ROOT) lines. 4. Revert _import_from_us_cores helper and all 3 call sites — root cause fix (preventing sys.modules pollution) makes them unnecessary. 5. Revert us_trigger_batch.py changes — not affected by this bug. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…sion-imports fix: US module namespace collision — cores/ import resolution failure
Summary
When running
us_stock_analysis_orchestrator.py --mode morning, twoImportErrors occur because Python's module cache resolvesfrom cores.xxxagainst the main project'scores/instead ofprism-us/cores/.Root Cause
Early imports (e.g.,
TelegramConfig) cause Python to cache the main project'scorespackage. Once cached, all subsequentfrom cores.xxx import ...statements resolve againstcores/at the project root — even when the target module only exists inprism-us/cores/.Errors
us_stock_analysis_orchestrator.py:242→
prefetch_us_macro_intelligence_dataexists inprism-us/cores/data_prefetch.py, not incores/data_prefetch.pyus_trigger_batch.py:37→
us_surge_detector.pyexists only inprism-us/cores/, not in maincores/Fix
Use
importlib.util.spec_from_file_location()to bypass Python's module cache and load directly fromprism-us/cores/. This is the same pattern already used by_import_from_main_cores()for the reverse direction (loading maincores/modules from withinprism-us/).us_stock_analysis_orchestrator.py: Added_import_from_us_cores()helper (counterpart to_import_from_main_cores()) and replaced the failing import at line 242us_trigger_batch.py: Replacedfrom cores.us_surge_detector import ...with file-based import usingimportlib.utilRelated
us_pending_order_batch.py(sys.path order causing import failure)Test plan
python3 prism-us/us_stock_analysis_orchestrator.py --mode morningruns without ImportError🤖 Generated with Claude Code