๐Ÿผ ๋ฐฑ์•ค๋“œ/Fast API

Fast API - ๊ธฐ๋Šฅ

๊ณ„๋ž€์†Œ๋…„ 2025. 6. 23. 10:45

 

์ด์ „ ๊ธ€

https://koreatstm.tistory.com/295

 

Fast API - ๊ธฐ์ดˆ

๋งŽ์€ ํ”„๋ ˆ์ž„ ์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ด์™”์ง€๋งŒ, ๊นŠ์ด๊ฐ€ ๋ถ€์กฑํ•˜๋‹ค๊ณ  ํ•ญ์ƒ ์ƒ๊ฐํ–ˆ๋‹ค. ์ด์ œ๋Š” ๊ทธ๊ฒƒ๋“ค์— ๋Œ€ํ•œ ๊นŠ์ด๋ฅผ ๋” ์ฑ„์›Œ๋ณด๊ณ ์ž ํ•œ๋‹ค.๋ฌด์—‡๋ณด๋‹ค ์–ด๋–ค ์งˆ๋ฌธ์— ๊ด€ํ•ด์„œ๋“  ์ด์œ ๋ฅผ ๋ฌผ์„ ๋•Œ ๋ฐ”๋กœ ๋‹ต ํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค

koreatstm.tistory.com

 

ํ…œํ”Œ๋ฆฟ

 

Jinja2๋ž€?

  • Jinja2๋Š” Python ์›น ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋™์  ์›น ํŽ˜์ด์ง€ ์ƒ์„ฑ์„ ์œ„ํ•œ ํ…œํ”Œ๋ฆฟ ์—”์ง„
  • ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ ๋‚ด์—์„œ {{ ๋ณ€์ˆ˜๋ช… }}์œผ๋กœ ๋™์  ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ๊ฐ€๋Šฅ
  • ์ฃผ์š” ๊ตฌ๋ถ„ ๊ธฐํ˜ธ
    • {{...}}: ๋ณ€์ˆ˜๋‚˜ ํ‘œํ˜„์‹ ์ถœ๋ ฅ
    • {%...%}: ์ œ์–ด๋ฌธ ์ž‘์„ฑ ์‹œ ์‚ฌ์šฉ 
    • {#...#}: ์ฃผ์„ ์ฒ˜๋ฆฌ

 

FastAPI์—์„œ Jinja2 ํ…œํ”Œ๋ฆฟ ์‚ฌ์šฉ

  • templates ๋””๋ ‰ํ„ฐ๋ฆฌ
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/")
def read_root(request: Request):
    items = ["์‚ฌ๊ณผ", "๋ฐ”๋‚˜๋‚˜", "์˜ค๋ Œ์ง€"]
    return templates.TemplateResponse("index.html", {"request": request, "items": items})
<!DOCTYPE html>
<html>
    <body>
        <ul>
          {% for item in items %}
            <li>{{ item }}</li>
          {% endfor %}
        </ul>
    </body>
</html>

์ด๋•Œ: {{username}} ๋ถ€๋ถ„์ด ์‹ค์ œ ๊ฐ’์œผ๋กœ ๋Œ€์ฒด๋˜์–ด ๋ Œ๋”๋ง ๋จ

ํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง: HTML ํ…œํ”Œ๋ฆฟ์— ๋ฐ์ดํ„ฐ ์‚ฝ์ž…ํ•˜๋ ค๋ฉด TemplateResponse๋ฉ”์„œ๋“œ ์‚ฌ์šฉ

  • ๋ Œ๋”๋ง: ๋ณ€์ˆ˜๋‚˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ„ ํ…œํ”Œ๋ฆฟ์„ ์‹ค์ œ ๊ฐ’์ด ์ฑ„์›Œ์ง„ ์™„์„ฑ๋œ ๋ฌธ์„œ(HTML ๋“ฑ)๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ
  • FastAPI์—์„œ๋Š” Jinja2Templates ํด๋ž˜์Šค๋กœ ํ…œํ”Œ๋ฆฟ ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฐ ์˜ต์…˜ ์ง€์ • ํ›„, TemplateResponse๋กœ HTML ๋ Œ๋”๋ง
  • ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์ „๋‹ฌ๋  ๋ณ€์ˆ˜๋“ค์„ ๋‹ด์€ ๋”•์…”๋„ˆ๋ฆฌ {request~ username}์„ ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ ๋ฐ›์•„์„œ, ์ด๋ฅผ ๋™์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

HTML ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ(Jinja2)์— ๋™์  ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด ์›น ๋ธŒ๋ผ์šฐ์ €์— ๋ณด์—ฌ์ค„ ๋•Œ ์‘๋‹ตํ•˜๋Š” ๊ฐ์ฒด

์ด๋•Œ, ๋ฐ˜๋“œ์‹œ request ๊ฐ์ฒด๋ฅผ context์— ํฌํ•จ์‹œ์ผœ์•ผํ•œ๋‹ค. ๋‘˜์ด ์„ธํŠธ!!

TemplateResponse๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ Request ๊ฐ์ฒด๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ์— ํฌํ•จ์‹œ์ผœ์•ผ ํ•œ๋‹ค. Request๊ฐ์ฒด์— ์˜์กดํ•˜๊ธฐ ๋•Œ๋ฌธ

 

Jinja2Templates์˜ ์ฃผ์š” ์˜ต์…˜

์˜ต์…˜๋ช… ์„ค๋ช… ๊ธฐ๋ณธ๊ฐ’
directory ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์ด ์œ„์น˜ํ•œ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ฒฝ๋กœ "templates"
encoding ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์˜ ์ธ์ฝ”๋”ฉ ๋ฐฉ์‹ "utf-8"
auto_reload ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๋กœ๋“œํ• ์ง€ ์—ฌ๋ถ€ False
  • ์ฃผ์š” ์˜ต์…˜: directory(๊ฒฝ๋กœ), encoding(์ธ์ฝ”๋”ฉ), auto_reload(์ž๋™ ์žฌ๋กœ๋”ฉ)
  • templates ๋””๋ ‰ํ„ฐ๋ฆฌ ๋‚ด์˜ HTML ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ๋™์ ์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•ด ๋ฐ˜ํ™˜

 

# ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜
@app.get("/user/{username}")
def root(request: Request,username:str):
    return templates.TemplateResponse("index.html", {"request": request, "username":username})

# ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ 
# /user?username=ํ™๊ธธ๋™ → username์ด "ํ™๊ธธ๋™"์œผ๋กœ ํ…œํ”Œ๋ฆฟ์— ์ „๋‹ฌ๋จ
@app.get("/user")
def root(request: Request, username: str):
    return templates.TemplateResponse("index.html", {"request": request, "username": username})

 

1. ํ•„ํ„ฐ: ๋ณ€์ˆ˜์— ํŒŒ์ดํ”„(|) ๊ธฐํ˜ธ๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ’์„ ๋ณ€ํ™˜ 

{{ username|lower }}   {# ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ #}
{{ price|round(2) }}   {# ์†Œ์ˆ˜์  ๋‘˜์งธ์ž๋ฆฌ๊นŒ์ง€ ๋ฐ˜์˜ฌ๋ฆผ #}

2. ๋งคํฌ๋กœ: ๋ฐ˜๋ณต๋˜๋Š” ํ…œํ”Œ๋ฆฟ ์ฝ”๋“œ๋ฅผ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์žฌ์‚ฌ์šฉ

<!-- macros.html ๋งคํฌ๋กœ ์„ ์–ธ --> 
{% macro print_name(name) %}
  <p>์ด๋ฆ„์€ {{ name }} ์ž…๋‹ˆ๋‹ค.</p>
{% endmacro %}

<!-- index.html ๋งคํฌ๋กœ ํ˜ธ์ถœ -->
{% from "macros.html" import print_name %}
{{ print_name("ํ™๊ธธ๋™") }}
{{ print_name("๊น€์ฒ ์ˆ˜") }}

3. block: ํ…œํ”Œ๋ฆฟ ์ƒ์†์—์„œ ์ž์‹ ํ…œํ”Œ๋ฆฟ์ด ๋‚ด์šฉ์„ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ์„ ์ง€์ • -> ํ—ค๋”, ํ‘ธํ„ฐ๋ฅผ ๊ณตํ†ต ์‚ฌ์šฉ(์ €๋ฒˆ ๋งํ•ด๋ฐฉ ํ”„๋กœ์ ํŠธ์—์„œ ์•„์ฃผ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ–ˆ๋‹ค.)

{# base.html #}
<body>
  {% block content %}{% endblock %} <!-- ์ƒ์†๋ฐ›๋Š” ํ…œํ”Œ๋ฆฟ์—์„œ ๋‚ด์šฉ ์ถ”๊ฐ€-->
</body>

{# index.html #}
{% extends "base.html" %}

{% block content %}
  <h1>์—ฌ๊ธฐ์— ๋‚ด์šฉ ์ž‘์„ฑ</h1>
{% endblock %}

4. import: ๋‹ค๋ฅธ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์˜ ๋งคํฌ๋กœ๋‚˜ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ด

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/")
def read_root(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

index.html

{% import "macros.html" as macros %}

<h2>import๋กœ ๋งคํฌ๋กœ ์‚ฌ์šฉ ์˜ˆ์‹œ</h2>

{{ macros.print_name("ํ™๊ธธ๋™") }}
{{ macros.print_name("๊น€์ฒ ์ˆ˜") }}

macros.html

{% macro print_name(name) %}
  <p>์ด๋ฆ„์€ {{ name }} ์ž…๋‹ˆ๋‹ค.</p>
{% endmacro %}

5. set: ํ…œํ”Œ๋ฆฟ ๋‚ด์—์„œ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•˜๊ฑฐ๋‚˜ ๊ฐ’์„ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉ

{% set total = price * quantity %}
์ดํ•ฉ: {{ total }}

6. do: ํ‘œํ˜„์‹์˜ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•˜์ง€ ์•Š๊ณ , ๋ถ€์ˆ˜ ํšจ๊ณผ๋งŒ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ

{% do my_list.append('item') %}

7. with: ๋ธ”๋ก ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ž„์‹œ ๋ณ€์ˆ˜ ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉ

{% with total=price*quantity %}
  ์ดํ•ฉ: {{ total }}
{% endwith %}

 

์ •์  ํŒŒ์ผ

 

์ •์  ํŒŒ์ผ(static files)์ด๋ž€?

  • ์„œ๋ฒ„๊ฐ€ ๋ณ„๋„์˜ ์ฒ˜๋ฆฌ ์—†์ด ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ํŒŒ์ผ 
  • FastAPI์—์„œ๋Š” starlette์˜ StaticFiles ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด ์ •์  ํŒŒ์ผ์„ ์‰ฝ๊ฒŒ ์„œ๋น„์Šคํ•  ์ˆ˜ ์žˆ๋‹ค.
    • FastAPI๋Š” Starlette์˜ StaticFiles๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์™€์„œ, ๊ฐœ๋ฐœ์ž๊ฐ€ ๋” ํŽธํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ fastapi.staticfiles๋กœ ํ•œ ๋ฒˆ ๋” ๊ฐ์‹ผ ๊ฒƒ๋ฟ์ด๋‹ค.
    • ์ฆ‰, ๋‘˜ ์ค‘ ์–ด๋А ๊ฒƒ์„ ์จ๋„ ๋™์ž‘๊ณผ ๊ธฐ๋Šฅ, ๋‚ด๋ถ€ ๊ตฌํ˜„์ด ์™„์ „ํžˆ ๋™์ผ
      • from fastapi.staticfiles import StaticFiles
      • from starlette.staticfiles import StaticFiles
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()

# 'static' ํด๋”๋ฅผ '/static' ๊ฒฝ๋กœ๋กœ ์„œ๋น„์Šค
app.mount("/static", StaticFiles(directory="static"), name="static")

templates = Jinja2Templates(directory="templates")

@app.get("/")
def read_root(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})
<!DOCTYPE html>
<html>
<head>
    <title>์ •์  ํŒŒ์ผ </title>
</head>
<body>
    <h1>์ •์  ํŒŒ์ผ(static files) ์‚ฌ์šฉ </h1>
    <img src="/static/test.png" alt="์ด๋ฏธ์ง€">
</body>
</html>

 

APIRouter

 

APIRouter๋ž€?

  • APIRouter๋Š” FastAPI์—์„œ ์—ฌ๋Ÿฌ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ฌถ์–ด์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํด๋ž˜์Šค
  • ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ๋ผ์šฐํ„ฐ๋ฅผ ํŒŒ์ผ๋ณ„/๊ธฐ๋Šฅ๋ณ„๋กœ ๋ถ„๋ฆฌํ•ด app.include_router()๋กœ ๋ฉ”์ธ ์•ฑ์— ๋“ฑ๋กํ•œ๋‹ค.
  • ์ด๋ฅผ ํ†ตํ•ด ๋ผ์šฐํŠธ ์ˆ˜์ค€์—์„œ ์˜์กด์„ฑ๊ณผ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฏธ๋“ค์›จ์–ด: ํŠน์ • ๊ฒฝ๋กœ ์ž‘๋™์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜๊ธฐ ์ „, ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ๋™์ž‘ํ•˜๋Š” ํ•จ์ˆ˜. ๋˜ํ•œ ๋ชจ๋“  ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋˜๊ธฐ ์ „์—๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.
      • ๋ฏธ๋“ค์›จ์–ด๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์˜ค๋Š” ์š”์ฒญ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
      • ์š”์ฒญ์„ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ๋กœ ์ž‘๋™์œผ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ฒ˜๋ฆฌ
      • ์‘๋‹ต์„ ๋ฐ˜ํ™˜
  • APIRouter์™€ ๋ฏธ๋“ค์›จ์–ด
    • FastAPI์˜ ๋ฏธ๋“ค์›จ์–ด๋Š” ์•ฑ ์ „์ฒด์— ์ ์šฉ๋˜๋ฉฐ, ๋ผ์šฐํ„ฐ๋ณ„๋กœ ์ง์ ‘ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์ ์šฉํ•  ์ˆ˜๋Š” ์—†๋‹ค.
    • ํ•˜์ง€๋งŒ APIRouter์˜ dependencies ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด, ๋ผ์šฐํ„ฐ ๋‹จ์œ„๋กœ ๊ณตํ†ต ๋กœ์ง(์˜ˆ: ์ธ์ฆ, ๊ถŒํ•œ ์ฒดํฌ ๋“ฑ)์„ "์˜์กด์„ฑ ํ•จ์ˆ˜"๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์˜์กด์„ฑ ํ•จ์ˆ˜๋Š” ๋ฏธ๋“ค์›จ์–ด์™€ ๋น„์Šทํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ, ๋ผ์šฐํ„ฐ๋ณ„/์—”๋“œํฌ์ธํŠธ๋ณ„๋กœ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ๋‹ค๋ฅด๋‹ค.
from fastapi import FastAPI, APIRouter

app = FastAPI()
router = APIRouter(prefix="/users", tags=["users"])

@router.get("/")
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]

app.include_router(router)
  • prefix="/users": ๋ชจ๋“  ๊ฒฝ๋กœ์— /users๊ฐ€ ์ ‘๋‘์‚ฌ๋กœ ๋ถ™์Œ (/users/)
  • tags=["users"]: API ๋ฌธ์„œ์—์„œ ๊ทธ๋ฃนํ•‘์— ์‚ฌ์šฉ
items_router = APIRouter(prefix="/items", tags=["items"])

@items_router.get("/")
async def read_items():
    return [{"item_id": "Foo"}, {"item_id": "Bar"}]

app.include_router(router)
app.include_router(items_router)
  • ์—ฌ๋Ÿฌ ๋ผ์šฐํ„ฐ๋ฅผ ๊ฐ๊ฐ ๋‹ค๋ฅธ ์ ‘๋‘์‚ฌ๋กœ ๋“ฑ๋ก ๊ฐ€๋Šฅ
  • ๋™์ผ ๋ผ์šฐํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ ์ ‘๋‘์‚ฌ๋กœ ์ค‘๋ณต ๋“ฑ๋กํ•ด API ๋ฒ„์ „ ๊ด€๋ฆฌ๋„ ๊ฐ€๋Šฅ

 

1. ๊ธฐ๋ณธ FastAPI ๋ผ์šฐํ„ฐ

from fastapi import FastAPI

app = FastAPI()

@app.get("/users")
def get_users():
    return [{"name": "Alice"}]

@app.get("/items")
def get_items():
    return [{"item": "Book"}]
  • ๋ชจ๋“  ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํ•œ ํŒŒ์ผ์— ๋ชจ์—ฌ ์žˆ์Œ
  • ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ  ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์›€

 

2. APIRouter ์‚ฌ์šฉ

# user_router.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/")
def get_users():
    return [{"name": "Alice"}]
# items_router.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/")
def get_items():
    return [{"item": "Book"}]
# main.py

from fastapi import FastAPI
from users_router import router as users_router
from items_router import router as items_router

app = FastAPI()
app.include_router(users_router, prefix="/users")
app.include_router(items_router, prefix="/items")
  • ๊ฐ ๊ธฐ๋Šฅ๋ณ„๋กœ ํŒŒ์ผ์„ ๋‚˜๋ˆ ์„œ ๊ด€๋ฆฌ
  • ๋ฉ”์ธ ์•ฑ์— include_router๋กœ ๋“ฑ๋ก
  • ๋ผ์šฐํ„ฐ๋ณ„๋กœ prefix, ํƒœ๊ทธ, ๋ฏธ๋“ค์›จ์–ด ๋“ฑ ๋ณ„๋„ ์„ค์ • ๊ฐ€๋Šฅ
  • ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด: APIRouter๋กœ ๊ธฐ๋Šฅ๋ณ„๋กœ ๋ถ„๋ฆฌ·๋ชจ๋“ˆํ™”ํ•ด์•ผ ์œ ์ง€๋ณด์ˆ˜์™€ ํ™•์žฅ์„ฑ์ด ํ›จ์”ฌ ์ข‹์•„์ง

 

1. @app.~ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ

@app.get("/hello")
def hello():
    return {"msg": "hello"}
  • ์„ค๋ช…: FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(app)์— ์ง์ ‘ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹
  • ์šฉ์–ด: "์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ผ์šฐํ„ฐ", "๋ฉ”์ธ ๋ผ์šฐํ„ฐ" ๋ผ ํ•œ๋‹ค.

2. @router.~ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ

from fastapi import APIRouter
router = APIRouter()

@router.get("/foo")
def foo():
    return {"msg": "foo"}
  • ์„ค๋ช…: APIRouter ๊ฐ์ฒด์— ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹
  • ์šฉ์–ด: "๋ฏธ๋“ค์›จ์–ด ๋ผ์šฐํ„ฐ", "๋ผ์šฐํ„ฐ ๊ฐ์ฒด", "์„œ๋ธŒ ๋ผ์šฐํ„ฐ", "๊ธฐ๋Šฅ๋ณ„ ๋ผ์šฐํ„ฐ" ๋ผ ํ•œ๋‹ค.
from fastapi import FastAPI, APIRouter

app = FastAPI()
router = APIRouter()

@app.get("/main")
def main_route():
    return {"msg": "main route"}

@router.get("/sub")
def sub_route():
    return {"msg": "sub route"}

app.include_router(router, prefix="/api")

 

3. ๋ฏธ๋“ค์›จ์–ด ์„ค์ •

  • FastAPI๋Š” ๋ผ์šฐํ„ฐ ๋‹จ์œ„๋กœ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ง์ ‘ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค.
  • ๋ฏธ๋“ค์›จ์–ด๋Š” FastAPI ์ธ์Šคํ„ด์Šค(app)์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋ฉฐ, ์ด ์ธ์Šคํ„ด์Šค์— ์—ฌ๋Ÿฌ APIRouter๋ฅผ ํฌํ•จ์‹œํ‚จ๋‹ค.
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app.add_middleware(
    TrustedHostMiddleware, allowed_hosts=["localhost", "example.com"]
)
  • TrustedHostMiddleware๋Š” ์š”์ฒญ์˜ Host ํ—ค๋”๊ฐ€ ์ง€์ •๋œ ๋„๋ฉ”์ธ/์„œ๋ธŒ๋„๋ฉ”์ธ์ผ ๋•Œ๋งŒ ํ—ˆ์šฉํ•˜๋Š” ๋ณด์•ˆ ๊ธฐ๋Šฅ

 

4. ์˜์กด์„ฑ ํ•จ์ˆ˜(Depends)์™€ APIRouter

  • ์˜์กด์„ฑ ํ•จ์ˆ˜๋Š” ๋ผ์šฐํ„ฐ ์ „์ฒด ๋˜๋Š” ํŠน์ • ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ๊ณตํ†ต ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋Š” ํ•จ์ˆ˜
from fastapi import FastAPI, APIRouter, Depends, HTTPException, Header

# 1. ํ† ํฐ ์ฒดํฌ ํ•จ์ˆ˜
def token_check(authorization: str = Header(None)):
    if authorization != "Bearer mysecrettoken":
        raise HTTPException(status_code=401, detail="Invalid or missing token")

# 2. /api ์ „์šฉ ๋ผ์šฐํ„ฐ ์ƒ์„ฑ, ์˜์กด์„ฑ ๋ถ€์ฐฉ
api_router = APIRouter(
    prefix="/api",
    dependencies=[Depends(token_check)]
)

@api_router.get("/hello")
def hello():
    return {"message": "API ํ† ํฐ ์ธ์ฆ ์„ฑ๊ณต!"}

# 3. ์ผ๋ฐ˜ ๋ผ์šฐํ„ฐ (ํ† ํฐ ์ฒดํฌ ์—†์Œ)
public_router = APIRouter()

@public_router.get("/public")
def public():
    return {"message": "๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ"}

# 4. ์•ฑ์— ๋ผ์šฐํ„ฐ ๋“ฑ๋ก
app = FastAPI()
app.include_router(api_router)      # /api/* ๊ฒฝ๋กœ๋งŒ ํ† ํฐ ์ฒดํฌ
app.include_router(public_router)   # /public ๊ฒฝ๋กœ๋Š” ํ† ํฐ ์ฒดํฌ ์—†์Œ
  • token_check ํ•จ์ˆ˜: ์š”์ฒญ ํ—ค๋”์˜ Authorization ๊ฐ’์ด "Bearer mysecrettoken"์ด ์•„๋‹ˆ๋ฉด 401 ์—๋Ÿฌ ๋ฐœ์ƒ
  • dependencies=[Depends(token_check)]: ์ด ๋ผ์šฐํ„ฐ์˜ ์—”๋“œํฌ์ธํŠธ์—์„œ token_check๊ฐ€ ์ž๋™์œผ๋กœ ์‹คํ–‰๋จ
    • /public → ํ† ํฐ ์—†์ด ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ
    • /api/hello → ํ† ํฐ์ด ๋งž์•„์•ผ ์ ‘๊ทผ ๊ฐ€๋Šฅ

 

  • ๋ผ์šฐํ„ฐ์— ๋“ฑ๋กํ•˜๋ฉด ํ•ด๋‹น ๋ผ์šฐํ„ฐ์˜ ๋ชจ๋“  ์—”๋“œํฌ์ธํŠธ์—์„œ ์ž๋™ ์ ์šฉ๋œ๋‹ค.
  • ์˜์กด์„ฑ ์ฃผ์ž…(Dependency Injection, DI)
    • ์ฝ”๋“œ๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์™ธ๋ถ€ ์ž์›์„ ์ง์ ‘ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๊ณ , ์™ธ๋ถ€์—์„œ(ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€) ๋Œ€์‹  ๋งŒ๋“ค์–ด์„œ "์ฃผ์ž…"ํ•ด์ฃผ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋ฒ•
  • ์˜์กด์„ฑ: ์–ด๋–ค ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค๊ฐ€ ๋™์ž‘ํ•˜๋ ค๋ฉด ํ•„์š”ํ•œ ๊ฒƒ(์˜ˆ: DB ์—ฐ๊ฒฐ, ์ธ์ฆ ์ •๋ณด, ๊ณตํ†ต ํŒŒ๋ผ๋ฏธํ„ฐ ๋“ฑ)
  • ์ฃผ์ž…: ๊ทธ ํ•„์š”ํ•œ ๊ฒƒ์„ ์ง์ ‘ ๋งŒ๋“ค์ง€ ์•Š๊ณ , ์™ธ๋ถ€์—์„œ(์ฃผ๋กœ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€) ๋Œ€์‹  ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ
  • ์™œ ์“ฐ๋Š”๊ฐ€?
    • ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋จ
  • ์—ฌ๋Ÿฌ ์—”๋“œํฌ์ธํŠธ์—์„œ ๋ฐ˜๋ณต๋˜๋Š” ๋กœ์ง(์˜ˆ: DB ์—ฐ๊ฒฐ, ์ธ์ฆ, ๊ณตํ†ต ํŒŒ๋ผ๋ฏธํ„ฐ ๋“ฑ)์„ ํ•œ ๋ฒˆ์— ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
  • ๊ฐ ํ•จ์ˆ˜๊ฐ€ "๋ฌด์—‡์ด ํ•„์š”ํ•œ์ง€"๋งŒ ์„ ์–ธํ•˜๋ฉด, ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ๊ฐ€์ ธ์˜ค๋Š”์ง€๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ฒ˜๋ฆฌ

 

1. Depends๋ž€?

  • FastAPI์—์„œ ์˜์กด์„ฑ ์ฃผ์ž…(Dependency Injection)์„ ์œ„ํ•œ ํ•จ์ˆ˜
  • ์ธ์ฆ, DB ์—ฐ๊ฒฐ, ๊ณตํ†ต ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋“ฑ "์—ฌ๋Ÿฌ ์—”๋“œํฌ์ธํŠธ์—์„œ ๋ฐ˜๋ณต๋˜๋Š” ๊ณตํ†ต ๋กœ์ง"์„ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์—”๋“œํฌ์ธํŠธ ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ผ์šฐํ„ฐ, ์•ฑ ์ „์ฒด์— ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค.

2. APIRouter์— Depends ์ ์šฉ

router = APIRouter(dependencies=[Depends(ํ•จ์ˆ˜)])

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, ํ•ด๋‹น ๋ผ์šฐํ„ฐ์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์—”๋“œํฌ์ธํŠธ์—์„œ ์ง€์ •ํ•œ ์˜์กด์„ฑ ํ•จ์ˆ˜๊ฐ€ ์ž๋™์œผ๋กœ ์‹คํ–‰๋œ๋‹ค.

 

5. ๋ผ์šฐํŠธ ์„ค์ • ์ƒ์†

  • ์ƒ์œ„ ๋ผ์šฐํ„ฐ(APIRouter)์— ์„ค์ •ํ•œ prefix, tags, dependencies ๋“ฑ์€ ํ•˜์œ„ ๋ผ์šฐํ„ฐ/์—”๋“œํฌ์ธํŠธ์— ์ƒ์†๋œ๋‹ค.
  • ์—ฌ๋Ÿฌ APIRouter๋ฅผ ๊ณ„์ธต์ ์œผ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
from fastapi import FastAPI, APIRouter, Depends

def auth_dependency():
    print("์ธ์ฆ ์ฒดํฌ ์‹คํ–‰!")

# ํ•˜์œ„ ๋ผ์šฐํ„ฐ ์ •์˜
sub_router = APIRouter()

@sub_router.get("/profile")
def get_profile():
    return {"msg": "ํ”„๋กœํ•„ ์ •๋ณด"}

# ์ƒ์œ„ ๋ผ์šฐํ„ฐ ์ •์˜ (prefix, tags, dependencies ์„ค์ •)
parent_router = APIRouter(
    prefix="/users",
    tags=["users"],
    dependencies=[Depends(auth_dependency)]
)

# ํ•˜์œ„ ๋ผ์šฐํ„ฐ๋ฅผ ์ƒ์œ„ ๋ผ์šฐํ„ฐ์— ํฌํ•จ (include_router)
parent_router.include_router(sub_router, prefix="/me", tags=["private"])

# FastAPI ์•ฑ์— ์ƒ์œ„ ๋ผ์šฐํ„ฐ๋ฅผ ํฌํ•จ
app = FastAPI()
app.include_router(parent_router)
  • ์ƒ์œ„ ๋ผ์šฐํ„ฐ(parent_router)
    • prefix: /users
    • tags: ["users"]
    • dependencies: [Depends(auth_dependency)] (๋ชจ๋“  ํ•˜์œ„ ๋ผ์šฐํŠธ์— ์ธ์ฆ ์ฒดํฌ ์ ์šฉ)
  • ํ•˜์œ„ ๋ผ์šฐํ„ฐ(sub_router)
    • /profile ์—”๋“œํฌ์ธํŠธ๋งŒ ์ •์˜
  • ๋ผ์šฐํ„ฐ ๊ณ„์ธต ๊ตฌ์กฐ
    • /users/me/profile
      • tags: ["users", "private"]
      • dependencies: auth_dependency๊ฐ€ ์ž๋™ ์‹คํ–‰๋จ
  • /users/me/profile๋กœ ์š”์ฒญ ์‹œ:auth_dependency()๊ฐ€ ๋จผ์ € ์‹คํ–‰๋จ (์˜์กด์„ฑ ์ƒ์†)์‹ค์ œ ์—”๋“œํฌ์ธํŠธ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋จSwagger UI์—์„œ tags๋„ ์ƒ์†/๋ณ‘ํ•ฉ๋จ

 

๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ

 

BackgroundTasks๋ž€?

  • FastAPI ๋‚ด์žฅ ํด๋ž˜์Šค
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ, ์‘๋‹ต์„ ๋จผ์ € ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…์„ ์‹คํ–‰
  • HTTP ์‘๋‹ต์„ ๋จผ์ € ๋ณด๋‚ด๊ณ  ์„œ๋ฒ„์—์„œ ๊ณ„์† ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ
  • ์˜ˆ์‹œ: ์ด๋ฉ”์ผ ๋ฐœ์†ก, ๋กœ๊ทธ ๊ธฐ๋ก ๋“ฑ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ์ง€๋งŒ ์ฆ‰์‹œ ์‘๋‹ต์ด ํ•„์š”ํ•œ ์ž‘์—…์— ์ ํ•ฉ

์žฅ์ 

  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ์‚ฌ์šฉ์ž ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ตœ์†Œํ™”
  • ๋ฆฌ์†Œ์Šค ์ตœ์ ํ™”: ์„œ๋ฒ„๊ฐ€ ๊ธด ์ž‘์—…์— ๋ฌถ์ด์ง€ ์•Š์Œ
  • ๊ฐ„๋‹จํ•œ ์ž‘์—…์€ Celery ๊ฐ™์€ ์™ธ๋ถ€ ํ ์—†์ด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

์‚ฌ์šฉ๋ฒ• ๋ฐ ์ธ์ž ์ „๋‹ฌ

1. ํ•จ์ˆ˜๋งŒ ์ „๋‹ฌ

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def simple_task():
    print("๋‹จ์ˆœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์‹คํ–‰")

@app.get("/only-func")
async def only_func(background_tasks: BackgroundTasks):
    background_tasks.add_task(simple_task)
    return {"message": "ํ•จ์ˆ˜๋งŒ ์ „๋‹ฌ"}

2. ์œ„์น˜๊ธฐ๋ฐ˜ ์ธ์ž ์ „๋‹ฌ

def task_with_args(a, b):
    print(f"a: {a}, b: {b}")

@app.get("/args")
async def with_args(background_tasks: BackgroundTasks):
    background_tasks.add_task(task_with_args, 10, 20)
    return {"message": "์œ„์น˜ ์ธ์ž ์ „๋‹ฌ"}

3. ํ‚ค์›Œ๋“œ ์ธ์ž ์ „๋‹ฌ

def task_with_kwargs(a, b=0):
    print(f"a: {a}, b: {b}")

@app.get("/kwargs")
async def with_kwargs(background_tasks: BackgroundTasks):
    background_tasks.add_task(task_with_kwargs, a=5, b=15)
    return {"message": "ํ‚ค์›Œ๋“œ ์ธ์ž ์ „๋‹ฌ"}

4. ํ•จ์ˆ˜+์œ„์น˜+ํ‚ค์›Œ๋“œ ์ธ์ž ํ˜ผํ•ฉ ์ „๋‹ฌ

def mixed_task(a, b, c=0):
    print(f"a: {a}, b: {b}, c: {c}")

@app.get("/mixed")
async def mixed(background_tasks: BackgroundTasks):
    background_tasks.add_task(mixed_task, 1, 2, c=3)
    return {"message": "ํ˜ผํ•ฉ ์ธ์ž ์ „๋‹ฌ"}

 

  • BackgroundTasks๋กœ ๋น„๋™๊ธฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌ
  • add_task(func, *args, **kwargs)๋กœ ๋‹ค์–‘ํ•œ ์ธ์ž ์ „๋‹ฌ ๊ฐ€๋Šฅ
  • ์‘๋‹ต๊ณผ ๋™์‹œ์— ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—… ์‹คํ–‰, ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ํšจ์œจ์  ์‚ฌ์šฉ

 

์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต

 

์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์ด๋ž€?

  • ์„œ๋ฒ„๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋ชจ๋‘ ๋ณด๋‚ด๋Š” ๋Œ€์‹ , ๋ฐ์ดํ„ฐ๋ฅผ ์ผ์ • ๋‹จ์œ„(์ฒญํฌ, ํ–‰, ํ”„๋ ˆ์ž„)๋กœ ๋‚˜๋ˆ„์–ด ์ˆœ์ฐจ์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ์ „์†กํ•˜๋Š” ๋ฐฉ์‹

์™œ ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์„ ์“ฐ๋‚˜?

  • ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ: ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋ชจ๋‘ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฌ์ง€ ์•Š๊ณ , ์ƒ์„ฑ๋˜๋Š” ๋Œ€๋กœ ๋ฐ”๋กœ๋ฐ”๋กœ ์ „์†ก
  • ๋ ˆ์ดํ„ด์‹œ ๊ฐ์†Œ: ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋˜๋Š” ์ฆ‰์‹œ ๋จผ์ € ์ „์†ก๋˜์–ด, ์‚ฌ์šฉ์ž๋Š” ๋น ๋ฅด๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Œ
  • ๋Œ€์šฉ๋Ÿ‰ ์ฒ˜๋ฆฌ: ์ˆ˜์‹ญ~์ˆ˜๋ฐฑ MB ์ด์ƒ์˜ ํŒŒ์ผ, ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋“ฑ๋„ ๋ฌด๋ฆฌ ์—†์ด ์ฒ˜๋ฆฌ

 

StreamingResponse์˜ ๋™์ž‘ ์›๋ฆฌ

  • StreamingResponse๋Š” FastAPI๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํŠน์ˆ˜ ์‘๋‹ต ํด๋ž˜์Šค
  • ๋‚ด๋ถ€์ ์œผ๋กœ ์ดํ„ฐ๋Ÿฌ๋ธ”(์ œ๋„ˆ๋ ˆ์ดํ„ฐ, ํŒŒ์ผ ๊ฐ์ฒด ๋“ฑ)์„ ๋ฐ›์•„, yield๋กœ ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ ์กฐ๊ฐ์„ ํด๋ผ์ด์–ธํŠธ์— ์ˆœ์ฐจ์ ์œผ๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค.
  • yield๋Š” ํ•จ์ˆ˜ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘์ง€ํ•˜๊ณ , ๊ฐ’์„ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌํ•œ ๋’ค ๋‹ค์Œ ํ˜ธ์ถœ ์‹œ ๊ทธ ๋‹ค์Œ ์ค„๋ถ€ํ„ฐ ๊ณ„์† ์‹คํ–‰ํ•œ๋‹ค.
  • media_type(์˜ˆ: "text/csv", "application/json", "video/mp4" ๋“ฑ)๊ณผ headers๋ฅผ ์ง€์ •ํ•ด, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ ํ˜•์‹๊ณผ ํŒŒ์ผ๋ช…์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ธ์‹ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import csv
import io

app = FastAPI()

def csv_row_generator():
    # ํ—ค๋”
    output = io.StringIO()
    writer = csv.writer(output)
    writer.writerow(["id", "name", "score"])
    yield output.getvalue()
    output.seek(0)
    output.truncate(0)

    # ๋ฐ์ดํ„ฐ ํ–‰์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ์ƒ์„ฑ
    for i in range(1, 101):
        writer.writerow([i, f"user_{i}", i * 10])
        yield output.getvalue()
        output.seek(0)
        output.truncate(0)

@app.get("/download/csv")
def download_csv():
    headers = {
        "Content-Disposition": 'attachment; filename="data.csv"'
    }
    return StreamingResponse(
        csv_row_generator(),
        media_type="text/csv",
        headers=headers
    )
  • csv_row_generator
    • io.StringIO ๊ฐ์ฒด์— csv.writer๋กœ ํ•œ ์ค„์”ฉ ๋ฐ์ดํ„ฐ๋ฅผ ์”€
    • ๊ฐ ํ–‰์ด ์™„์„ฑ๋  ๋•Œ๋งˆ๋‹ค yield๋กœ ํ˜„์žฌ๊นŒ์ง€์˜ ๋‚ด์šฉ์„ ๋ฐ˜ํ™˜
    • yield ์ดํ›„ StringIO๋ฅผ ๋น„์›Œ์„œ ๋‹ค์Œ ํ–‰ ์ค€๋น„
  • StreamingResponse
    • generator๋ฅผ ๋ฐ›์•„์„œ, ํด๋ผ์ด์–ธํŠธ์— ํ•œ ์ค„์”ฉ(ํ˜น์€ ์ฒญํฌ ๋‹จ์œ„๋กœ) ์ „์†ก
    • media_type๊ณผ headers๋กœ ํŒŒ์ผ ํ˜•์‹๊ณผ ํŒŒ์ผ๋ช… ์ง€์ •
  • ํด๋ผ์ด์–ธํŠธ UX
    • ์‚ฌ์šฉ์ž๋Š” ๋‹ค์šด๋กœ๋“œ๊ฐ€ ์‹œ์ž‘๋˜์ž๋งˆ์ž ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ถ€ํ„ฐ ๋ฐ”๋กœ ๋ฐ›๊ธฐ ์‹œ์ž‘
    • ๋Œ€์šฉ๋Ÿ‰ CSV๋„ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ์—†์ด ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

 

์›น ์†Œ์ผ“

 

์›น์†Œ์ผ“์ด๋ž€?

  • ์‹ค์‹œ๊ฐ„, ์–‘๋ฐฉํ–ฅ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ
    • ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด๋ž€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ณ  ์„œ๋ฒ„๋„ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค.
    • ๋‹จ์ผ ์ฐฝ๋งŒ ์—ด์–ด๋„, ๋ธŒ๋ผ์šฐ์ €์—์„œ ์„œ๋ฒ„๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ณ  ์„œ๋ฒ„๊ฐ€ ์‘๋‹ต ๋ฉ”์‹œ์ง€๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
    • ์ฆ‰๋‹จ์ผ ์—ฐ๊ฒฐ์—์„œ๋„ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ์„œ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์›น์†Œ์ผ“์˜ "์–‘๋ฐฉํ–ฅ ํ†ต์‹ "
  • ์›น์†Œ์ผ“์€ ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ € ๋“ฑ)์™€ ์„œ๋ฒ„๊ฐ€ ํ•œ ๋ฒˆ ์—ฐ๊ฒฐ๋˜๋ฉด, ๊ทธ ์—ฐ๊ฒฐ์„ ๊ณ„์† ์œ ์ง€ํ•˜๋ฉฐ ์„œ๋กœ ์ž์œ ๋กญ๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํ”„๋กœํ† ์ฝœ
  • HTTP์™€ ๋‹ฌ๋ฆฌ, ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ์ˆ˜๋„ ์žˆ๊ณ , ํด๋ผ์ด์–ธํŠธ๋„ ์–ธ์ œ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • ์™œ ์“ฐ๋Š”๊ฐ€?
    • ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†ก์ด ํ•„์š”ํ•  ๋•Œ(์ฑ„ํŒ…, ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ, ๊ฒŒ์ž„, ์ฃผ์‹ ์ •๋ณด ๋“ฑ)
    • HTTP๋ณด๋‹ค ํ—ค๋”๊ฐ€ ์ž‘๊ณ , ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ ๋ฐ˜๋ณต์ ์ธ ์—ฐ๊ฒฐ/ํ•ด์ œ์— ๋“œ๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์ค„์–ด๋“ฆ(ํšจ์œจ์„ฑ)
  • ํ•ธ๋“œ์…ฐ์ดํฌ(Handshake) ๊ณผ์ •
    • ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ์€ ์ฒ˜์Œ์—” HTTP๋กœ ์‹œ์ž‘ํ•ด์„œ, ํŠน์ˆ˜ํ•œ ํ—ค๋”(Connection: Upgrade, Upgrade: websocket ๋“ฑ)๋ฅผ ํ†ตํ•ด ์›น์†Œ์ผ“์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ๋œ๋‹ค.
    • ์„œ๋ฒ„๊ฐ€ 101 Switching Protocols๋กœ ์‘๋‹ตํ•˜๋ฉด, ๊ทธ ๋’ค๋กœ๋Š” ์›น์†Œ์ผ“ ํ”„๋กœํ† ์ฝœ๋กœ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์ด ์‹œ์ž‘๋œ๋‹ค.
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
<head>
  <title>WebSocket Test</title>
</head>
<body>
  <h1>WebSocket Test</h1>
  <div>
    <button onclick="connectWS()">์—ฐ๊ฒฐ</button>
    <button onclick="disconnectWS()">์—ฐ๊ฒฐ ํ•ด์ œ</button>
    <input type="text" id="msgInput" placeholder="๋ฉ”์‹œ์ง€ ์ž…๋ ฅ">
    <button onclick="sendMsg()">์ „์†ก</button>
  </div>
  <div>
    <span id="status">์—ฐ๊ฒฐ ์ƒํƒœ: ๋Š๊น€</span>
  </div>
  <ul id="log"></ul>
  <script>
    let ws = null;
    function connectWS() {
      ws = new WebSocket("ws://localhost:8000/ws");
      ws.onopen = () => document.getElementById('status').innerText = "์—ฐ๊ฒฐ ์ƒํƒœ: ์—ฐ๊ฒฐ๋จ";
      ws.onclose = () => document.getElementById('status').innerText = "์—ฐ๊ฒฐ ์ƒํƒœ: ๋Š๊น€";
      ws.onmessage = (event) => {
        const log = document.getElementById('log');
        const li = document.createElement('li');
        li.innerText = event.data;
        log.appendChild(li);
      };
    }
    function disconnectWS() {
      if (ws) ws.close();
    }
    function sendMsg() {
      if (ws && ws.readyState === WebSocket.OPEN) {
        const msg = document.getElementById('msgInput').value;
        ws.send(msg);
      }
    }
  </script>
</body>
</html>
"""

@app.get("/")
async def get():
    return HTMLResponse(html)

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"๋ฐ›์€ ๋ฉ”์‹œ์ง€: {data}")
    except WebSocketDisconnect:
        pass
  1. ์›น์†Œ์ผ“ ๋ผ์šฐํŠธ ์ƒ์„ฑ: @app.websocket("/ws") ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์›น์†Œ์ผ“ ๋ผ์šฐํŠธ ์ƒ์„ฑ
  2. ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ์ •์˜: ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
  3. ์—ฐ๊ฒฐ ์ˆ˜๋ฆฝ: await websocket.accept()๋กœ ์—ฐ๊ฒฐ์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜๋ฝ
  4. ๋ฐ์ดํ„ฐ ์†ก์ˆ˜์‹  ๋ฌดํ•œ ๋ฃจํ”„์—์„œ await websocket.receive_text()๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๊ณ ,await websocket.send_text()๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  5. ์—ฐ๊ฒฐ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ์„ ๋Š์œผ๋ฉด WebSocketDisconnect ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ, ์ด๋ฅผ ์žก์•„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 

์ฐธ๊ณ 

https://fastapi.tiangolo.com/advanced/middleware/

https://fastapi.tiangolo.com/tutorial/static-files/

 

์ง€์†์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ ์ค‘์ธ ๊ธ€์ž…๋‹ˆ๋‹ค.