Skip to content

Unable to import module ‘main’ 에러 해결

This entry is part 2 of 2 in the series Fastapi + Serverless, AWS Lambda에 배포하기

위 글에서 부터 이어지는 내용으로 Lambda에 Fastapi서버를 배포하는 중에 생긴 Unable to import module ‘main’ 에러 해결에 대한 내용이다.

Serverless프레임워크를 통해 배포를 진행했고, 터미널에서 정상적으로 배포가 완료되었다는 로그가 떠서 생성된 function url로 접속을 해보았지만 에러가 발생했다.

cloudwatch를 통해 아래 log를 확인할 수 있었다.

[ERROR] Runtime.ImportModuleError: Unable to import module ‘main’: attempted relative import with no known parent package

로컬에서는 잘 작동되던 서버였기에 배포과정에서 문제가 생겼을 거라 생각해서 serverless부터 lambda, mangum등 공식문서와 이슈를 한참을 뒤지다가 아래 글을 발견했다.

https://velog.io/@anjaekk/python%EC%A0%88%EB%8C%80%EA%B2%BD%EB%A1%9C%EC%83%81%EB%8C%80%EA%B2%BD%EB%A1%9C-%EC%83%81%EB%8C%80%EA%B2%BD%EB%A1%9C-import-%EC%97%90%EB%9F%AC%EC%9D%B4%EC%9C%A0%EC%99%80-%ED%95%B4%EA%B2%B0

if __name__ == '__main__':

예전에 Python으로 프로그래밍을 할때 위와 같은 문법을 자주 썼는데 실제로 저게 무엇을 의미하는지에 대해서는 깊게 생각하지 않고 그냥 사용해 왔다. 덕분에 간단한 배포를 하는데 이틀넘게 이 부분에 매달렸던 것 같다. 그동안 쌓아왔던 업보를 이번에 청산했다..

먼저 “__name__”에 대해서 알아보자.

“__name__”은 Python에 내장되어있는 변수로 해당 Python모듈(.py파일)의 이름을 저장한다. 특이한 점은 같은 모듈이라도 실행되는 방식에 따라 “__name__”이 다른 값을 갖게 된다는 것이다. 만약 test라는 모듈이 있고 이 모듈(해당 파일)안에서 if __name__ == ‘main‘: 문법이 사용된 경우 __name__은 test혹은 main이 된다.

각각의 경우에 대해 살펴보자면,

  1. 모듈이 인터프리터에 의해 직접 실행되는 경우
    – 예를 들어 vscode에서 해당 파일을 열고 직접 실행시키는 경우이다. 이 때에는 name은 main이 된다.
    디렉토리의 안쪽 깊숙히 숨어있는 파일이라도 직접 해당 파일을 실행하면 인터프리터는 그 모듈이 최상위 모듈이라고 인식하고 최상위 모듈의 이름은 항상 “main”이다. 따라서 “if name == ‘main‘:”은 참이 되고 if 문 안의 코드가 실행된다.
  2. 해당모듈이 다른 상위 모듈에 import되어 사용되는 경우
    – 모듈안에 “if name == ‘main‘:” 부분이 있더라도 다른 모듈에 import되면 해당 코드는 실행되지 않는다. 최상위 모듈이 따로 있는 경우에는 경우1 에서 말했듯이 해당 모듈의 이름이 “main”이 되고, 다른 모듈들은 원래 자신의 이름을 가지게 되면서 if문이 거짓이 되기 때문이다.

if name == ‘main’문이 서버코드에 들어있지는 않았지만 아마 Fastapi서버를 실행시키는 과정에서 내부적으로 main모듈을 실행시키는 과정에서 위 에러가 발생하지 않았나 싶다.

하지만 로컬에서 서버를 실행시킬 때는 정상적으로 작동하던게 이상해서 GPT에게 물어보니 아래와 같이 답변했다.

로컬 환경에서 상대경로가 제대로 작동했던 이유는 파이썬 인터프리터의 동작 원리와 로컬 환경의 설정에 기인합니다. 아래의 이유들을 통해 이를 설명하겠습니다:

  1. 파이썬 모듈 검색 경로 (sys.path): 파이썬은 모듈을 검색할 때 sys.path라는 리스트를 사용합니다. 이 리스트에는 파이썬 인터프리터가 모듈을 찾을 디렉토리들이 포함되어 있습니다. 로컬 환경에서 상대경로가 동작했다면, 현재 작업 디렉토리가 sys.path에 포함되어 있었을 가능성이 높습니다.
  2. __main__ 모듈의 동작: 상대경로는 현재 모듈의 이름을 기반으로 경로를 결정합니다. 로컬 환경에서 스크립트를 직접 실행할 때, 스크립트가 메인 모듈이 되고 __name__은 “__main__”으로 설정됩니다. 이 경우, 현재 모듈의 이름이 “__main__”이 되기 때문에 상대경로가 동작할 수 있습니다.
  3. 상대경로의 시작점: 로컬 환경에서 스크립트를 실행할 때, 스크립트 파일이 위치한 디렉토리가 현재 작업 디렉토리로 자동 설정됩니다. 이로 인해 스크립트 파일이 상대경로로 다른 모듈을 import할 때, 현재 작업 디렉토리를 기준으로 상대경로가 작동하게 됩니다.
  4. 상대경로의 제한 범위: 로컬 환경에서는 주로 작은 프로젝트나 스크립트를 다루기 때문에, 상대경로가 제한 범위 내에서 작동할 수 있습니다. 즉, 모든 모듈이 같은 상위 디렉토리에 위치하고 있을 때, 로컬 환경에서는 이러한 구성이 동작할 수 있습니다.

서버 환경이나 대규모 프로젝트에서는 상대경로의 사용을 지양하고, 절대경로를 사용하여 모듈을 import하는 것이 좋습니다. 서버 환경에서는 sys.path에 현재 작업 디렉토리가 자동으로 추가되지 않을 수 있으며, 프로젝트 규모가 커질수록 상대경로를 사용하는 것이 모듈의 위치를 파악하기 어려워질 수 있습니다. 이에 따라 안정성을 위해 절대경로를 사용하는 것이 권장됩니다.

Python은 sys.path에 디렉토리를 포함시켜 상대경로를 인식한다고 한다. Lambda환경에서는 sys.path가 어떻게 설정되는지 다시 물어보니 아래와 같은 답변을 내놓았다.

  1. Lambda 환경의 sys.path: Lambda 함수가 실행될 때 sys.path는 특정 디렉토리만을 포함합니다. 이 디렉토리에는 Lambda 런타임 및 Lambda 함수 코드가 저장되어 있습니다. 즉, Lambda 환경에서는 기본적으로 사용자 정의 모듈이나 패키지의 디렉토리가 sys.path에 추가되지 않습니다.
  2. 상대경로와 모듈 위치: 상대경로로 모듈을 불러올 때, 현재 작업 디렉토리가 기준이 됩니다. Lambda 환경에서는 현재 작업 디렉토리가 Lambda 함수 코드가 위치한 디렉토리가 되기 때문에, 상대경로로 다른 모듈을 불러올 때 해당 모듈이 있는 디렉토리를 찾을 수 없어 발생하는 문제입니다.

Lambda에서는 기본적으로 사용자 정의 모듈이나 패키지의 디렉토리가 추가되지 않는다고 한다. 그래서 상대경로를 인식하지 못했고, 결과적으로 Unable to import module ‘main’ 에러가 발생한 듯 하다.

해결방법은 직접 sys.path에 추가되도록 Lambda의 Layer에 추가설정을 하거나 절대경로를 이용하면 된다. GPT의 답변처럼 상대경로 사용을 지양하고 절대경로를 사용하여 import하는 것이 깔끔한 해결책 같다.

배포중에 생긴 에러이기도 했고 서버리스로 배포하니 로그를 찾기도 힘들고 에러 내용이 직관적으로 다가오지도 않아서 디버깅하는게 어려웠다.

Series Navigation<< Fastapi + Serverless, AWS Lambda에 배포하기

Leave a Reply

Your email address will not be published. Required fields are marked *