# PyInstaller frozen 환경과 일반 실행 환경 모두에서 올바른 기준 경로 반환 # # 두 종류의 루트: # BASE_PATH : 사용자가 보는 실제 폴더(쓰기 가능). 로그/변경된 설정/저장된 모델이 여기로. # - dev 모드: __main__ 파일 위치 (= 프로젝트 루트) # - EXE(--onefile) 모드: EXE가 놓인 폴더 (예: E:\ANT\dist) # BUNDLE_PATH : 번들 안의 읽기 전용 자원 위치. # - dev 모드: BASE_PATH 와 동일 # - EXE 모드: PyInstaller 임시 추출 폴더 (sys._MEIPASS) import os import sys def get_base_path() -> str: """사용자 데이터 위치(읽기/쓰기).""" if getattr(sys, "frozen", False): return os.path.dirname(os.path.abspath(sys.executable)) main_mod = sys.modules.get("__main__") main_file = getattr(main_mod, "__file__", None) if main_file: return os.path.dirname(os.path.abspath(main_file)) # path_helper.py lives in utils/ — go up one level to reach the project root return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def get_bundle_path() -> str: """번들된 자원 위치(읽기 전용). EXE에선 _MEIPASS, dev에선 BASE_PATH.""" return getattr(sys, "_MEIPASS", BASE_PATH) BASE_PATH = get_base_path() BUNDLE_PATH = get_bundle_path() def get_path(*paths) -> str: """경로 결합. BASE_PATH 기준 우선, 거기 없으면 BUNDLE_PATH 폴백. 파일이 양쪽에 다 없으면 BASE_PATH 기준 경로 반환(신규 생성 시 사용자 영역에 만들도록).""" if not paths: return BASE_PATH primary = os.path.join(BASE_PATH, *paths) if os.path.exists(primary): return primary if BUNDLE_PATH and BUNDLE_PATH != BASE_PATH: fallback = os.path.join(BUNDLE_PATH, *paths) if os.path.exists(fallback): return fallback return primary