Fast API

현서·2025년 7월 19일

파이썬 웹서비스

목록 보기
7/7
post-thumbnail

1. Fast API

FastAPI는 빠르고 간단하게 API 서버를 만들 수 있게 해주는 파이썬 웹 프레임워크이다.

FastAPI의 특징

특징설명
빠르다이름처럼 빠른 속도를 자랑한다. (비동기 처리도 가능)
자동 문서 생성API 문서를 자동으로 만들어준다. (/docs에 들어가면 Swagger UI가 짠!)
타입 힌트 기반Python 타입 힌트로 유효성 검사와 문서까지 처리해준다.
JSON 쉽게 처리API는 주로 JSON 데이터를 주고받는데, FastAPI는 이것을 쉽게 해준다.
테스트 용이Postman이나 Swagger로 바로바로 테스트 가능

타입 힌트(Type Hint)란?
파이썬에서 이 변수나 함수는 어떤 자료형(타입)을 써야 하는지 미리 알려주는 표시이다.


2. 설치

FastAPI를 사용하려면 먼저 FastAPI 패키지를 설치해야한다.

pip install fastapi

FastAPI 애플리케이션을 실행하기 위해 uvicorn이라는 ASGI 서버가 필요하다.

Uvicorn은 Python으로 작성된 가볍고 빠른 웹 서버로, 비동기 웹 애플리케이션과 API를 실행하는 데 사용된다.

개발할 때는 자동 리로드 기능으로 편리하게 작업할 수 있고, 운영 환경에서는 다중 프로세스로 높은 성능을 제공한다.

pip install "uvicorn[standard]"

uvicorn[standard]
1. 이 패키지는 uvicorn에 기본적으로 포함된 기능 외에도 추가적인 표준 미들웨어를 포함한다.
2. 표준 미들웨어는 보안, 로깅 및 기타 서버 관련 기능을 추가하는 데 도움이 된다.
3. ex) Gzip 압축, CORS(Cross-Origin Resource Sharing) 지원 등이 기본적으로 포함되어 있다.

uvicorn
1. 기본적으로 필요한 최소한의 기능만을 제공하는 패키지이다.
2. uvicorn[standard]에 비해 미들웨어가 적고, 기본적인 서버 기능만을 제공한다.


ASGI 서버

ASGI(Asynchronous Server Gateway Interface) 서버는 Python에서 비동기 웹 애플리케이션을 실행할 수 있게 해주는 서버이다.

구분설명
WSGI오래된 방식, 동기 방식 웹 서버 규칙 (예: Django, Flask)
ASGI최신 방식, 비동기 처리 가능 (예: FastAPI, Starlette 등)

FastAPI 같은 최신 웹 프레임워크가 ASGI 서버와 잘 어울리며, 대표적인 서버로 Uvicorn과 Daphne가 있다.
→ 많은 요청을 동시에 처리하고, 실시간 기능이 필요한 앱을 쉽게 만들 수 있다.


3. 기본 예제

myapp/
├── main.py
├── static/
│   └── script.js
├── templates/
│   └── index.html

main.py

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()

# http://localhost:8080/static
# 정적 파일 제공
app.mount("/static", StaticFiles(directory="static"), name="static")

# HTML 템플릿 제공
templates = Jinja2Templates(directory="templates")

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

@app.get("/api/data")
async def get_data():
    return {"message": "FastAPI에서 보내는 데이터입니다"}

script.js

document.addEventListener("DOMContentLoaded", () => {
  fetch("/api/data")
    .then((response) => response.json())
    .then((data) => {
      document.getElementById("result").textContent = data.message;
    })
    .catch((error) => {
      document.getElementById("result").textContent = "데이터 불러오기 실패";
      console.error(error);
    });
});

index.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>FastAPI Frontend</title>
    <script defer src="/static/script.js"></script>
  </head>
  <body>
    <h1>프론트엔드 페이지</h1>
    <p id="result">데이터를 불러오는 중...</p>
  </body>
</html>

4. 실행

uvicorn main:app --reload

FastAPI는 자동으로 생성된 Swagger UI를 통해 API 문서를 제공한다.
(http://127.0.0.1:8000/docs 또는 http://127.0.0.1:8000/redoc 에서 확인 가능)

→ Swagger UI를 통해 API의 엔드포인트, 매개변수, 응답 형식 등을 살펴볼 수 있다.


REST와 RESTful API

REST(Representational State Transfer) : 어떻게 자원(웹에서 주고받는 데이터)을 주고받을지 정해놓은 규칙

규칙설명
URI는 자원을 표현/users, /posts/3
HTTP 메서드로 동작 표현GET, POST, PUT, DELETE 등
상태 없이 동작요청할 때마다 독립적이어야 함 (세션X)
JSON 형식으로 데이터 전달대부분 JSON 사용

RESTful API : REST 아키텍처를 따르는 웹 서비스의 인터페이스.
클라이언트와 서버 간의 통신을 위한 규칙을 제공하며, 자원의 생성, 조회, 수정, 삭제(CRUD)와 같은 기본적인 데이터 조작을 위한 메서드를 제공한다.


HTTP Method

FastAPI에서는 각 메서드에 대응하는 데코레이터를 제공하여 해당 엔드포인트에 대한 핸들러 함수를 등록할 수 있다.

→ 데코레이터를 사용하여 각각의 HTTP 메서드에 대한 동작을 정의할 수 있다.


간단한 RESTful API 예시

server.py

from fastapi import FastAPI
from pydantic import BaseModel

users = {
    0: {"userid": "apple", "name": "김사과"},
    1: {"userid": "banana", "name": "반하나"},
    2: {"userid": "orange", "name": "오렌지"} 
}

application = FastAPI()

@application.get("/users/{id}")
def find_user(id: int):
    user = users.get(id)
    if user is None:
        return {"error": "해당 id 없음"}
    return user

@application.get("/users/{id}/{key}") # http://127.0.0.1:8000/users/2/userid # 경로를 통해 보냄
def find_user_by_key(id:int, key:str):
    user = users.get(id)
    if user is None or key not in user:
        return {"error": "잘못된 id 또는 key"}
    return user[key]

@application.get("/id-by-name") # 쿼리를 통해 보냄
def find_user_by_name(name: str):
    for idx, user in users.items():
        if user["name"]==name:
            return user
    return {"error": "데이터를 찾지 못함"}

class User(BaseModel):
    userid: str
    name: str

@application.post("/users/{id}")
def create_user(id: int, user: User):
    if id in users:
        return {"error": "이미 존재하는 키"}
    users[id] = user.model_dump()
    return {"success": "ok"}

# 사용자 수정
class UserForUpdate(BaseModel):
    userid: str | None = None
    name: str | None = None

@application.put("/users/{id}")
def update_user(id: int, user: UserForUpdate):
    if id not in users:
        return {"error": "id가 존재하지 않음"}
    
    if user.userid is not None:
        users[id]["userid"] = user.userid
    if user.name is not None:
        users[id]["name"] = user.name
    
    return {"success": "ok"}

# 사용자 삭제
@application.delete("/users/{id}")
def delete_user(id: int):
    if id not in users:
        return {"error": "존재하지 않는 사용자"}
    users.pop(id)
    return {"success": "ok"}

실행

uvicorn server:application --reload

ad_client.py

import gradio as gr
import requests

url = "http://127.0.0.1:8000/create_ad"

def generate_ad(product_name, details, tone_and_manner):
    try:
        response = requests.post(url, json={
            "product_name": product_name,
            "details": details,
            "tone_and_manner": ", ".join(tone_and_manner)
        })
        result = response.json()
        ad = result["ad"]
        datas = result["datas"]

        processed_datas = [[d['product_name'], d['details'], d['tone_and_manner'], d['ad']] for d in datas]

        return ad, processed_datas
    except:
        return "서버 연결 실패!", None
    
with gr.Blocks(title="광고 문구 생성기") as demo:
    gr.Markdown("광고 문구를 생성해주는 AI 서비스앱")

    with gr.Row():
        product_input = gr.Textbox(label="제품 이름", placeholder="예: 천연 치약")
        details_input = gr.Textbox(label="주요 내용", placeholder="자극 없는 천연 원료 치약")

    tone_options = gr.CheckboxGroup(
        label="광고 문구의 느낌",
        choices=["기본", "재밌게", "과장스럽게", "참신하게", "고급스럽게", "센스있게", "신선하게", "친근하게", "전문성있게"],
        value=["기본"]
    )

    generate_btn = gr.Button("광고 문구 생성하기")
    output_ad = gr.Textbox(label="생성된 광고 문구", lines=3)
    output_table = gr.DataFrame(label="세부 데이터", headers=["key", "value"], wrap=True)

    generate_btn.click(fn=generate_ad, inputs=[product_input, details_input, tone_options], outputs=[output_ad, output_table])

    demo.launch()

실행

python ad_client.py

ad_server.py

import os
from fastapi import FastAPI
from pydantic import BaseModel
from pymongo import MongoClient
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(api_key=os.getenv("API_KEY"))
url = os.getenv("DB_URL")
dbconn = MongoClient(url)

database = dbconn['aiproject']
collection = database['ad']

class AdGenerator:
    def __init__(self, engine='gpt-4.1-nano-2025-04-14'):
        self.engine = engine

    def using_llm(self, prompt):
        system_instruction = 'assistant는 마케팅 문구 작성 도우미로 동작한다. user의 내용을 참고하여 마케팅 문구를 작성해라'
        messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": prompt}]
        response = client.chat.completions.create(
            model=self.engine,
            messages=messages
        )
        return response.choices[0].message.content

    def generate(self, product_name, details, tone_and_manner):
        prompt = f'제품 이름: {product_name}\n주요내용: {details}\n 광고 문구의 스타일: {tone_and_manner} 위 내용을 참고하여 마케팅 문구를 만들어라'
        result = self.using_llm(prompt=prompt)
        return result
    
app = FastAPI()

class Product(BaseModel):
    product_name: str
    details: str
    tone_and_manner: str

@app.post("/create_ad")
async def create_ad(product: Product):
    ad_generator = AdGenerator()
    ag = ad_generator.generate(product_name=product.product_name, details=product.details, tone_and_manner=product.tone_and_manner)
    data_insert = {'product_name': product.product_name, 'details':product.details, 'tone_and_manner':product.tone_and_manner, 'ad': ag}
    result = collection.insert_one(data_insert)
    result = collection.find({})
    datas = []
    for data in result:
        data.pop('_id', None)
        datas.append(data)

    return {'ad': ag, 'datas': datas}

실행

uvicorn ad_server:app --reload
profile
The light shines in the darkness.

0개의 댓글