파이썬 다른 폴더 파일 import

seongmin0302·2025년 5월 5일
0

plango 프로젝트

목록 보기
7/10

FastAPI 기반으로 Plango 프로젝트를 개발하는 중, 아래 5개의 TourAPI 호출 테스트를 하고 싶었다.

  • searchKeyword1
  • detailCommon1
  • detailIntro1
  • detailInfo1
  • detailImage1

지금까지 작성한 FastAPI 코드들과는 무관한 테스트 파일들을 만들어, 각 API를 어떻게 호출할지 정하고 응답 구조가 어떻게 생겼는지 그때그때 마다 빠르게 확인하고 싶었다. 별도로 테스트용 프로젝트를 만들지 않고 Plango 프로젝트 내에서 각 API에 대한 테스트 파일을 만들어 각각에 파일에서 VSCode 실행 버튼 으로 곧바로 실행하여 테스트하고 싶었다.

하지만 이때 각 테스트 파일이 Plango 프로젝트의 환경 설정 파일(plango2025/config/settings.py)을 그대로 활용할 수 있었으면 좋겠었다!

settings.py

# 환경 설정값을 중앙에서 관리하기 위한 파일
# .env 환경변수 파일과 연동되어, 설정값을 안전하고 효율적으로 다룰 수 있도록 한다.

from pydantic_settings import BaseSettings
from pydantic import Field
from functools import lru_cache

class Settings(BaseSettings):
    # 기본 설정
    app_name: str = "Plango"
    debug: bool = True # FastAPI 실행 시 디버깅 정보 출력

    # TourAPI 설정
    tourapi_key: str = Field(..., env="TOURAPI_KEY") # .env 파일에 정의된 TOURAPI_KEY 값을 로드. 해당 키는 반드시 있어야 되며 없으면 에러 발생.
    tourapi_base_url: str = "https://apis.data.go.kr/B551011"

    # 외부 통신 설정
    timeout: int = 30  # 외부 API 호출시 타임아웃 시간

    class Config:
        env_file = ".env" # .env 파일에서 값을 로드하도록 지정
        env_file_encoding = "utf-8" # UTF-8로 인코딩된 파일을 읽게 설정.

# 매번 Settings()를 새로 만들면 비효율적이므로, lru_cache()로 1회 생성 후 재사용
# 다른 모듈에서 get_settings()를 호출하면 캐시된 동일한 설정 인스턴스를 받게 된다.
@lru_cache()
def get_settings():
    return Settings()

따라서 나는 5개의 테스트 파일을 작성했다.

아래는 그중detailIntro1-test.py이다. (plango2025-2\api-test\detailIntro1-test.py)

# 소개정보조회 테스트 코드 입니다.

import requests

from config.settings import get_settings

settings = get_settings()
service_key =  settings.tourapi_key

url = f"http://apis.data.go.kr/B551011/KorService1/detailIntro1?serviceKey={service_key}"

params = {
    "MobileApp": "plango",
    "MobileOS": "ETC",
    "numOfRows": 1,
    "pageNo": 1,
    "contentId": 126508,
    "contentTypeId": 12,
    "_type": "json"
}

response = requests.get(url, params=params)

print(response.status_code)

# dict = response.json()
# print(dict)

print(response.text)

하지만 에러 발생!

최상위 폴더 plango2025 파일아래 다음과 같은 파일이 있었다

api-test내의 detailCommon1-test.py 파일에서 vscode 의 실행버튼을 눌렀다.

하지만 아래와 같이
config내의 settings.py파일에 접근하려고 하니 에러가 발생했다.

에러발생


해결하기 !

나는 아래와 같이 해결했다.

아래 코드 위에

from config.settings import get_settings

이 코드를 추가하면 된다!

sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

결과적으로 테스트 파일중 하나인
detailCommon1-test.py에서 VSCode의 실행 버튼을 클릭했을 때
Plango 프로젝트의 설정 파일을 정상적으로 불러왔고, 잘 동작이 됐다!


참고 블로그

내가 만든 모듈(.py)을 불러 오는 두가지 방법

상위 폴더에 있는 파일 import - 예시1

Q. sys.path.append는 왜 해야돼?

모듈 경로(PATH)

모듈 경로(PATH)는 Pythonimport할 때 어디서 모듈을 찾을지를 저장해둔 검색 경로 목록을 의미한다.

즉, import a 같은 구문이 실행되면, 파이썬은 다음 순서대로 a.py를 찾는다 !

  1. 현재 실행 중인 파일이 있는 디렉터리
  2. 환경변수 PYTHONPATH에 등록된 경로
  3. 파이썬 설치 경로에 있는 site-packages 같은 기본 경로들

에러 원인

파이썬은 기본적으로 현재 실행 중인 파일 기준의 모듈 경로(PATH) 를 따라 import를 처리한다.
하지만 a.py가 이 경로들에 포함되어 있지 않다면, 그냥 import a를 했을 때

ModuleNotFoundError: No module named 'a'

와 같은 에러가 발생하게 된다.


해결 방법

import sys, os
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

이 코드에서 sys.path.append(...)는 다음과 같은 의미이다!

"야 파이썬아, 이 경로도 모듈 찾을 때 같이 봐줘!"

위 코드는 현재 파일 기준으로 한 단계 상위 폴더(folder1)의 경로를 수동으로 sys.path 에 등록해서, 그 안에 있는 모듈들(a.py, b.py)도 import할 수 있도록 해주는 방식!

sys.path
파이썬의 표준 라이브러리 sys 모듈 안에 있는 리스트 객체.
import 모듈 구문이 실행되면, 파이썬은 sys.path에 나열된 경로들을 차례로 탐색해서 모듈을 찾음. 리스트이기 때문에 앞에 있을수록 먼저 탐색됨.
우리가 흔히 말하는 모듈 검색 경로sys.path 리스트입니다.


sys.path를 우리가 명시적으로 작성하지 않아도, Python은 자동으로 내부적으로 항상 참조하고 있다. 파이썬은 import 구문을 처리할 때마다 자동으로 sys.path를 사용해서 모듈을 검색한다.




Q. 단계별로 분석해보자!

전체 코드:

sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

전체 동작 목적

  • folder2/c.pyfolder1/a.py를 가져오려면 folder1/의 경로가 sys.path에 포함되어 있어야 함.

단계별로 안쪽부터 바깥쪽까지 분석

1. __file__

  • 현재 실행 중인 파일(c.py)의 경로를 의미합니다.
    예:

    /Users/you/project/folder1/folder2/c.py

2. os.path.dirname(__file__)

  • __file__이 가리키는 파일의 디렉토리 경로만 추출합니다.
    예:

    os.path.dirname("/Users/you/project/folder1/folder2/c.py")
    → "/Users/you/project/folder1/folder2"

3. os.path.abspath(...)

  • 상대 경로일 수 있는 위 경로를 절대 경로로 변환합니다.
    이미 절대 경로면 그대로 리턴됩니다.
    예:

    os.path.abspath("/Users/you/project/folder1/folder2")
    → "/Users/you/project/folder1/folder2"

4. os.path.dirname(...) (다시 한 번)

  • 위에서 얻은 folder2 경로의 부모 디렉토리, 즉 folder1 경로를 추출합니다.
    예:

    os.path.dirname("/Users/you/project/folder1/folder2")
    → "/Users/you/project/folder1"

5. sys.path.append(...)

  • 마지막으로 /Users/you/project/folder1 경로를 Python의 모듈 탐색 경로(sys.path)에 추가합니다.
    이렇게 하면 import a처럼 상위 폴더에 있는 a.py도 문제 없이 import 할 수 있습니다.

파이썬은 sys.path.append()에서 절대 경로든 상대 경로든 받을 수 있다. 하지만 항상 절대 경로로 변환해서 넣는 게 안전하다 !!


최종 결과

위 코드를 실행하면 Python은 folder1 디렉토리에서 모듈을 탐색하게 되어, folder2/c.py에서 다음처럼 작성할 수 있습니다:

import a  # folder1/a.py

상위 폴더에 있는 파일 import - 예시2


Q. 지금까지 plango FastAPI 프로젝트에서 다른 폴더 파일 잘만 불러 왔는데?

Plango 프로젝트에서 지금까지 sys.path.append() 없이 잘 작동했던 이유는, FastAPI 앱을 실행할 때 “어디서 실행했느냐”가 중요하기 때문이다 !


핵심 개념: Python의 모듈 검색 경로(sys.path)

Python이 import할 때 모듈을 찾는 순서:

  1. 현재 실행 중인 파일이 있는 디렉토리
  2. PYTHONPATH 환경 변수에 등록된 경로
  3. 표준 라이브러리site-packages 같은 설치 경로

그래서 Plango에서는 왜 문제 없었을까?

uvicorn main:app --reload

이처럼 Plango 프로젝트 루트 디렉토리(main.py가 있는 최상위 프로젝트 폴더)에서 FastAPI 앱을 실행했기 때문에,
파이썬은 자동으로 다음 경로를 sys.path(모듈 검색 경로)의 첫 번째 항목으로 포함킨다!

C:\Users\jseon\OneDrive\바탕 화면\plango2025

이 경로에 schemas/, services/ 등이 포함되어 있으니까,
from schemas.. , from services.. 등을 전혀 문제없이 사용 할 수 있었던 것!

⁉️ 추후 팀원과 코드를 합친후 폴더 구조가 변경된 후!

C:\llm_project\plango2025\app\place_intro\api-test\detailCommon1-test.py를 실행시킬때

C:\llm_project\plango2025\app\config\settings.py를 가져와야 됐다!

나는 아래와 같이 코드를 작성했다.

detailCommon1-test.py

# 공통정보조회 테스트 코드 입니다.

import requests
import sys, os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)

from config.settings import settings


service_key =  settings.TOURAPI_KEY

url = f"http://apis.data.go.kr/B551011/KorService1/detailCommon1?serviceKey={service_key}"

params = {
    "MobileApp": "plango",
    "MobileOS": "ETC",
    "numOfRows": 1,
    "pageNo": 1,
    "contentId": 126508,
    "contentTypeId": 12,
    "defaultYN": "Y",
    "firstImageYN": "Y",
    "addrinfoYN": "Y",
    "mapinfoYN": "Y",
    "overviewYN": "Y",
    "areacodeYN": "N",
    "catcodeYN": "N",
    "_type": "json"
}

response = requests.get(url, params=params)


print(response.status_code)

# dict = response.json()
# print(dict)

print(response.text)

블로그에서는
os.path.abspath(__file__)를 중간에 사용했지만
나는 가장 먼저 os.path.abspath(__file__)로 무조건 절대 경로로 바꿔놓고, 그다음 os.path.dirname()을 타고 올라가는게 더 좋은 코드라고 생각했다!

결과적으로 잘 동작했다.

profile
컴튜터공학과 재학중

0개의 댓글