기존에 만들었던 pyinstaller 패키징에 에 많은 삽질이 있었지만 이 삽질은 거의 3일짜리였다..
기존 삽질 로그 : https://bslife.tistory.com/76?category=767584
에러 발생 과정
기존 패키징 파일은 pyinstaller를 이용하여 FastAPI(uvicorn) 를 빌드했었는데, 이 친구는 h2o AutoML을 배포하기 위한 파일이었다.
하지만 h2o AutoML 을 사용하면서 다음과 같은 세가지 문제가 있었다.
- pandas.Dataframe을 그대로 사용하지 않고 h2oFrame으로 변환 후 학습을 해야하고 예측을 해야하는 번거로움이 있음
- h2o는 java를 기반으로 하는 Lib이기 때문에 jdk가 존재해야하며, pyinstaller로 패키징 했을때 이 경로를 인식하지 못하여 일일히 설정해줘야 함. 또한 이 과정에서 패키징 파일의 크기가 상대적으로 커짐
- 간단한 모델임에도 불구하고 저장된 Model을 Load하고 Predict하는데 시간이 굉장히 오래걸림..! 10초정도..?
h2o 를 PyCaret으로 바꾸기로 결심하였다!
그리고 터진 Error.....
File "PyInstaller/loader/pyimod03_importers.py", line 540, in exec_module
File "pandas/io/formats/style.py", line 62, in <module>
File "pandas/io/formats/style.py", line 139, in Styler
File "jinja2/loaders.py", line 309, in __init__
ValueError: The 'pandas' package was not installed in a way that PackageLoader understands.
jinja2 Lib 의 PackageLoader가 pandas를 이해하지 못한다...
해결 과정
처음엔 jinja2/loaders.py lib를 열어봤다. 하지만 이 파일은 그저 파일을 읽어올 뿐, 별 다른 기능을 하지 않았다.
오류를 검색해본것 중에 패키지를 읽어올 때, __name__이랑 __package__ 이런 변수의 문제라는 이야기가 있어서 수정 후 빌드해봤지만 소용 없었고
Pyinstaller의 공통적인 문제가 빌드했을때 jinja2가 Package를 제대로 읽지 못하는 것이었는데 내 경우에는 pandas/io/formats/style.py 에서 문제가 발생했다.
기존 코드
# pandas/io/formats/style.py
loader = jinja2.PackageLoader("pandas", "io/formats/templates")
env = jinja2.Environment(loader=loader, trim_blocks=True)
print(f"###ENV### {env} ###")
template = env.get_template("html.tpl")
pandas 라이브러리 Package를 로드할때 위와 같이 불러오는데 Pyinstaller로 빌드하면 제대로 인식하지 못하기 때문에 빌드시에는 인식할 수 있도록 경로를 다르게 설정할 필요가 있다.
수정 후
import sys
if getattr(sys, 'frozen', False):
# we are running in a bundle
bundle_dir = sys._MEIPASS
print(f"###bundle_dir### {bundle_dir} ###")
loader = jinja2.FileSystemLoader(bundle_dir)
else:
# we are running in a normal Python environment
loader = jinja2.PackageLoader("pandas", "io/formats/templates")
env = jinja2.Environment(loader=loader, trim_blocks=True)
print(f"###ENV### {env} ###")
template = env.get_template("html.tpl")
pyinstaller가 빌드할때 사용되는 'frozen'으로 구분을 두고 빌드시 환경과 일반적인 환경에서의 실행과 나누었다.
빌드 될 때는 bundle_dir에 sys._MEIPASS를 인식하게 하고 최종적으로 필요한 파일을 참조할때 그 경로에 필요한 파일을 넣어주는 식으로 하면 될거라 생각했기때문에..!
결과적으로 "ValueError: The 'pandas' package was not installed in a way that PackageLoader understands." 문제는 해결이 되었고
loader 과정에서 원래는 io/formats/templates 내부에 있는 html.tpl 을 참조해야했지만 해당 파일이 없으므로 bundle_dir 위치를 출력해보고 그 위치에 html.tpl 를 추가해주면 깔끔하게 해결된다.
추가하는 방법은 main.spec 에서 datas에 넣어준 후 빌드하면 된다.
아래는 예시이다.
a = Analysis(['main.py'],
pathex=['/home/kbj/xedm/Scripts/fastapp'],
binaries=[],
datas=[
('./package/html.tpl','./')
]
./package/ 내부에 html.tpl을 복사해주었고 './'는 bundle_dir 이다.
Pyinstaller 하면 할수록 어려운 과제가 자꾸 생긴다.. pandas Lib까지 고쳐야 할 줄이야...
ps. 개발환경
참고로 작성일 기준 Python 3.9 버전에서는 pycaret이 동작하지 않는다. 강제로 내려야만 했음..ㅠㅠ
# OS
ubuntu == 20.04
# Python
version == 3.8.5
# Lib
pandas == 1.2.4
jinja2 == 3.0.1
pyinstaller == 4.3
pycaret == 2.3.1
'공부 > Python' 카테고리의 다른 글
[pandas] 대한민국 주소체계 분리해서 사전만들기 (0) | 2021.07.09 |
---|---|
[python] pyinstaller centOS에서 빌드하기(docker) (0) | 2021.06.25 |
[python] pyinstaller 와 빌드 후 ini 환경변수 읽기(konfig) (1) | 2021.06.19 |
[python] pyinstaller 일곱번의 삽질 for uvicorn + FastAPI (1) | 2021.06.18 |
[python] 클래스를 함수처럼 사용할수 있게.. __call__함수 (0) | 2021.06.15 |
댓글