๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ๐ŸŒฎ ๐Ÿ’ฌ
๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป/python

[python] Fastapi ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ exe ํŒŒ์ผ๋กœ ๋ฐฐํฌํ•˜๊ธฐ

by ๋ฐ”์ฟ„๋ฆฌ 2024. 11. 5.

๊ฐœ์š”

์›น ์„œ๋น„์Šค๋ฅผ ์ฝ”๋“œ ๊ณต๊ฐœ ์—†์ด ์™ธ๋ถ€์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก exe ํŒŒ์ผ๋กœ ๋ณ€ํ™˜ํ•˜๋ ค ํ•จ

ํ™•์ธ

Fastapi ์„œ๋ฒ„๋Š” python์œผ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ pyinstaller ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰ ํŒŒ์ผ์„ ํŒจํ‚ค์ง•ํ•˜๋ ค ํ•จ

์ง„ํ–‰

1. main.py ์—์„œ if __name__ == "__main__": ๋ธ”๋ก์œผ๋กœ ์‹คํ–‰๋˜๋Š” uvicorn ๋ถ„๋ฆฌํ•˜๊ธฐ

  • ์ด์œ 
    • pyinstaller๋Š” application์„ ํŒจํ‚ค์ง•ํ•  ๋•Œ model import ์ˆœ์„œ์™€ ์ฐธ์กฐ ๋ฐฉ์‹์— ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
    • if __name__ == "__main__": ๋ธ”๋ก์„ ์ •์ƒ์ ์œผ๋กœ ์ธ์‹ํ•˜์ง€ ๋ชปํ•ด์„œ ASGI ์„œ๋ฒ„(์ฆ‰, uvicorn)๋ฅผ ์‹คํ–‰ํ•˜์ง€ ๋ชปํ•œ๋‹ค.
  • ์ง„ํ–‰
    • main.py ์—์„œ๋Š” ๋‹จ์ˆœํžˆ Fastapi ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ •์˜ํ•˜๋Š” ์—ญํ• ๋งŒ ํ•˜๊ณ , run_server.py์—์„œ main.py๋ฅผ module๋กœ importํ•˜์—ฌ app์„ ์‹คํ–‰ํ•œ๋‹ค.

๋ณ€๊ฒฝ ์ „

main.py

import uvicorn
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from api.api import api_router

app = FastAPI(title="fastapi_app", openapi_url="/api/v1/openapi.json")
app.include_router(api_router)
app.mount("/static", StaticFiles(directory="static"), name="static")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

templates = Jinja2Templates(directory="templates")

@app.get("/", tags=["root"], response_class=HTMLResponse)
async def read_root(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

if __name__ == "__main__":
    uvicorn.run("main:app", host='0.0.0.0', port=8000)

 

๋ณ€๊ฒฝ ํ›„

main.py

from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from api.api import api_router

app = FastAPI(title="fastapi_app", openapi_url="/api/v1/openapi.json")
app.include_router(api_router)
app.mount("/static", StaticFiles(directory="static"), name="static")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

templates = Jinja2Templates(directory="templates")

@app.get("/", tags=["root"], response_class=HTMLResponse)
async def read_root(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

 

run_server.py

from main import app

if __name__ == "__main__":
    import asyncio
    from hypercorn.config import Config
    from hypercorn.asyncio import serve

    config = Config()
    config.bind = ["0.0.0.0:8000"]

    asyncio.run(serve(app, config))

 

โœ… uvicorn.run()์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  asyncio.run() ์œผ๋กœ ์‹คํ–‰ ์‹œ์ผฐ๋‹ค.

 

→ uvicorn.run() ๋Š” ์ž์ฒด์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด์™€๋Š” ๋ณ„๊ฐœ๋กœ pyinstaller ๋กœ ํŒจํ‚ค์ง•ํ•  ๋•Œ asyncio ์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์ค‘๋ณต๋˜์–ด ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. (ํŠนํžˆ, windows์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค.)

 

→ uvicorn.run()๋Š” ๋น„๋™๊ธฐ ํ™˜๊ฒฝ์—์„œ ๋™๊ธฐ์ ์œผ๋กœ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ, ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์ด๋ฅผ ๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์‹คํ–‰์‹œํ‚ค๋ ค๊ณ  ํ•œ๋‹ค. ํ•˜์ง€๋งŒ asyncio.run()์€ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ง์ ‘ ์ œ์–ดํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์—†๋Š” ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค. asyncio.run์„ ํ†ตํ•ด ๋ช…์‹œ์ ์œผ๋กœ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋น„๋™๊ธฐ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ์ถฉ๋Œ ์—†์ด ๋” ์•ˆ์ •์ ์œผ๋กœ ์ž‘๋™ํ•  ์žˆ๋‹ค!

 

2. ์‹คํ–‰ ํŒŒ์ผ์—์„œ static , templates ํด๋” ๊ฒฝ๋กœ ์„ค์ •

HTML ํ…œํ”Œ๋ฆฟ, CSS, JavaScript์™€ ๊ฐ™์€ ์ •์  ๋ฆฌ์†Œ์Šค๋ฅผ ํฌํ•จํ•˜๋Š” static๊ณผ templates ํด๋” FastAPI๊ฐ€ ์ด ํด๋”๋“ค์„ ํŠน์ • ๊ฒฝ๋กœ์—์„œ ์ฐพ๋Š”๋‹ค. PyInstaller๋กœ ํŒจํ‚ค์ง•ํ•  ๋•Œ๋Š” ์‹คํ–‰ ํŒŒ์ผ์ด ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ, static ๋ฐ templates ํด๋”์˜ ์œ„์น˜๋ฅผ ๋™์ ์œผ๋กœ ์„ค์ •ํ•ด ์ค˜์•ผ ํ•œ๋‹ค.

 

๋ณ€๊ฒฝ ์ „

# static
app.mount("/static", StaticFiles(directory="static"), name="static")

# templates
templates = Jinja2Templates(directory="templates")

 

๋ณ€๊ฒฝ ํ›„

# PyInstaller ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ์˜ ๊ฒฝ๋กœ ์„ค์ •
if getattr(sys, 'frozen', False):
    base_dir = sys._MEIPASS  # PyInstaller๊ฐ€ ์„ค์ •ํ•˜๋Š” ์ž„์‹œ ํด๋” ๊ฒฝ๋กœ
else:
    base_dir = os.path.dirname(os.path.abspath(__file__))

# static
app.mount("/static", StaticFiles(directory=os.path.join(base_dir, "static")), name="static")

# templates
templates = Jinja2Templates(directory=os.path.join(base_dir, "templates"))

 

โœ… ์ถ”๊ฐ€๋กœ ์ฐธ์กฐํ•˜๋Š” ํŒŒ์ผ์ด ์žˆ์œผ๋ฉด ๋™์ผํ•˜๊ฒŒ ๊ฒฝ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

→ json ํŒŒ์ผ์„ ์ฐธ์กฐํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์–ด์„œ ๋™์ผํ•˜๊ฒŒ ๊ฒฝ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค

if getattr(sys, 'frozen', False):
    base_dir = sys._MEIPASS
else:
    base_dir = os.path.dirname(os.path.abspath(__file__))

json_file_path = os.path.join(base_dir, "projects.json")

 

3. pyinstaller ์‹คํ–‰

1. pyinstaller ์„ค์น˜ํ•ด์ค€๋‹ค.

pip3 install pyinstaller

 

2. app์„ ์‹คํ–‰์‹œํ‚ค๋Š” run_server.py ์™€ ๋™์ผํ•œ ์œ„์น˜์—์„œ ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

- MacOS : --add-data ๋ถ€๋ถ„์— ":" ์‚ฌ์šฉ

pyinstaller --onefile --name fastapi_app --add-data "static:static" --add-data "templates:templates" --add-data "projects.json:." run_server.py

 

- Windows  : --add-data ๋ถ€๋ถ„์— ";" ์‚ฌ์šฉ

pyinstaller --onefile --name fastapi_app --add-data "static;static" --add-data "templates;templates" --add-data "projects.json;." run_server.py

 

์„ฑ๊ณต์ ์œผ๋กœ ์‹คํ–‰์ด ์™„๋ฃŒ๋˜๋ฉด 2๊ฐœ์˜ ํด๋”(build, dist), 1๊ฐœ์˜ spec ํŒŒ์ผ(fastapi_app.spec)์ด ์ƒ์„ฑ๋œ๋‹ค.

  1. build
    1. build ํด๋”๋Š” ํŒจํ‚ค์ง• ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ž„์‹œ ํŒŒ์ผ์„ ์ €์žฅํ•œ๋‹ค.
    2. ํŒจํ‚ค์ง•์ด ๋๋‚œ ํ›„์—๋Š” build ํด๋”๊ฐ€ ํ•„์š” ์—†์œผ๋ฏ€๋กœ ์‚ญ์ œํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค.
  2. dist
    1. dist ํด๋”๋Š” ์ตœ์ข… ํŒจํ‚ค์ง•๋œ ์‹คํ–‰ ํŒŒ์ผ์„ ์ €์žฅํ•˜๋Š” ์œ„์น˜
    2. --onefile ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ: dist ํด๋”์— ๋ฉ”์ธ ์‹คํ–‰ ํŒŒ์ผ๊ณผ ํ•จ๊ป˜ ๋ชจ๋“  ์˜์กด์„ฑ์ด ํฌํ•จ๋œ ํ•˜์œ„ ํด๋”๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.
    3. --onefile ์˜ต์…˜์„ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ: dist ํด๋”์— ๋‹จ์ผ .exe ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.
    4. ์ด ํด๋”๋Š” ์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ์ด๋ฏ€๋กœ, exe ํŒŒ์ผ์„ ๋‹ค๋ฅธ ์œ„์น˜์— ๋ณต์‚ฌํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  3. .spec ํŒŒ์ผ
    1. .spec ํŒŒ์ผ์€ PyInstaller์˜ ์„ค์ • ํŒŒ์ผ๋กœ, ํŒจํ‚ค์ง• ๊ณผ์ •์—์„œ PyInstaller๊ฐ€ ์‚ฌ์šฉํ•  ์„ค์ •์„ ํฌํ•จํ•œ๋‹ค.
    2. --add-data ์˜ต์…˜์œผ๋กœ ์ถ”๊ฐ€ํ–ˆ๋˜ ํŒŒ์ผ๋“ค, --hidden-import์œผ๋กœ ์ˆจ๊ฒจ์ง„ ์ž„ํฌํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋˜ ์ •๋ณด ๋“ฑ์ด ์ด ํŒŒ์ผ์— ์ €์žฅ๋œ๋‹ค.
    3. .spec ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์—ฌ ํŒจํ‚ค์ง• ์„ธ๋ถ€ ์„ค์ •์„ ๋ณ€๊ฒฝํ•˜๊ณ , ์ด ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด ๋‹ค์‹œ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค.
pyinstaller fastapi_app.spec

 

4. ์ƒ์„ฑ๋œ exe ํŒŒ์ผ ์‹คํ–‰

1. ์ƒ์„ฑ๋œ ํŒŒ์ผ์„ ๋”๋ธ” ํด๋ฆญํ•˜์—ฌ ์‹คํ–‰ํ•œ๋‹ค.

 

2. http://0.0.0.0:8000 ์œผ๋กœ ์„œ๋น„์Šค๊ฐ€ ์˜ฌ๋ผ๊ฐ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

3. ์„œ๋น„์Šค๊ฐ€ ์ž˜ ์šด์˜๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

๐Ÿ“Œ MacOS์—์„œ ์ƒ์„ฑํ•œ exe ํŒŒ์ผ์€ windows์—์„œ ์‹คํ–‰์ด ๋˜์ง€ ์•Š๋Š”๋‹ค. windows์—์„œ ์‹คํ–‰ํ•  exe ํŒŒ์ผ์€ windows์—์„œ ์ƒ์„ฑํ•ด์•ผํ•œ๋‹ค.

 

๐Ÿ“Œ windows ์—์„œ pyinstaller ์‹คํ–‰ ์‹œ ์œ ์˜ํ•  ์  2๊ฐ€์ง€

  1. pyinstaller๋Š” Python์˜ Scripts ๋””๋ ‰ํ† ๋ฆฌ์— ์„ค์น˜๋œ๋‹ค. ์ด ๊ฒฝ๋กœ๊ฐ€ Windows ํ™˜๊ฒฝ ๋ณ€์ˆ˜ PATH์— ์ถ”๊ฐ€๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, pyinstaller ๋ช…๋ น์–ด๋ฅผ ์ธ์‹ํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.
    1. Python Scripts ๊ฒฝ๋กœ ํ™•์ธํ•˜๊ณ  Windows ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ์ถ”๊ฐ€ํ•œ๋‹ค.
    2. ์ œ์–ดํŒ > ์‹œ์Šคํ…œ > ๊ณ ๊ธ‰ ์‹œ์Šคํ…œ ์„ค์ • > ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ด๋™ → PATH ๋ณ€์ˆ˜๋ฅผ ์ฐพ์•„ ํŽธ์ง‘ํ•œ ํ›„, C:\Users\YourUsername\AppData\Local\Programs\Python\Python39\Scripts ๊ฒฝ๋กœ๋ฅผ ์ƒˆ ์ค„๋กœ ์ถ”๊ฐ€ํ•˜๊ณ  ์ €์žฅ
  2. cmd ๋˜๋Š” PowerShell ์„ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰์‹œ์ผœ์„œ pyinstaller๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
    1. Windows์—์„œ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ๋‹ค.