본문 바로가기

내용 복습/python

FastAPI 공부내용 1일차

1. 모던 파이썬

1.1 가상환경

  • 파이썬은 가상환경을 지원하는데, pip가 다운로드한 패키지를 저장하기 위한 디렉터리다. 가상환경을 활성화하면 쉘은 파이썬 모듈을 로드할 때 이곳을 먼저 확인한다.
  • 가상환경을 설정할 때는 venv라는 모듈을 활용하며 독립된 프로그램으로도, 파이썬 모듈로도 실행가능하다.
- venv를 파이썬 모듈로 실행하는 명령어
python -m venv venv1

- 생성한 가상환경을 실행하는 쉘 명령어
source venv1/bin/activate

 

  • 위 명령어를 실행하여 가상환경에 진입하면 앞으로 pip install 명령을 수행할 때마다 venv1 디렉터리 하위에 패키지를 설치한다. 그리고 파이썬 프로그램을 실행하면 venv1에 있는 파이썬 인터프리터와 모듈이 실행된다.

1.2 Poetry

  • pip와 Poetry는 패키지를 다운로드하는 기능 외에 구성파일에서 여러 패키지를 관리하는 역할도 한다. pip에서는 requirements.txt, Poetry에서는 pyproject.toml 파일이 구성파일이다.
  • 둘 모두 패키지를 다운로드할 뿐만 아니라 패키지가 다른 패키지에 대해 가지는 까다로운 의존성을 관리한다. 
- Poetry 설치 명령어
pip install poetry

- poetry 이용
poetry install [설치하려는 패키지명]

 

 

2. FastAPI 둘러보기

2.1 FastAPI 소개

  • FastAPI는 2018년 발표한 파이썬기반 웹 프레임워크로, 파이썬3에 추가된 기능을 활용해 현대적이며, 뛰어난 성능과 빠른개발, 타입힌트와 Pydantic 모델을 이용한 코드품질을 자랑한다.
- 기본 파이썬 패키지 추천
pip install fastapi

pip install  uvicorn
(Uvicorn 웹 서버)

pip install httpie
(HTTPie 텍스트 웹 클라이언트)

pip install requests
(Requests 동기식 웹 클라이언트 패키지)

pip install httpx
(HTTPX 동기/비동기 웹 클라이언트 패키지)

 

  • 가장 잘 알려진 텍스트 웹 클라이언트는 curl이다. 하지만 HTTPie가 더 사용하기 쉽고, JSON인토딩과 디코딩을 사용하므로 FastAPI와 더 잘어울린다.
  • FastAPI에는 웹서버가 포함되지 않아 Uvicorn을 사용하는 것이 좋다.
- Uvicorn 실행 명령어(외부에서 시작)
uvicorn [파일명]:app --reload

 

아래는 app.py 내부에서 시작하는 방법이다. reload 인자는 hello.py가 변경되면 웹서버를 다시 시작하도록 Uvicorn에 지시한다. 예시로 본 파일명이 hello라서 아래와 같이 입력되었으며, 당연히 인자를 상세하게 설정하는 것도 가능하다. 기본적으로 8000번 포트를 사용하는데, 외부에서 시작하든 내부에서 시작하든 host, port인자를 사용하여 설정할 수 있다.

@app.get("/hi")
def greet():
  return "Hello? World?"

if __name__ == __main__":
  import uvicorn
  uvicorn.run("hello:app", reload=True)

 

FastAPI는 HTTP 요청을 구성하는 데이터를 직접 제공한다는 장점을 가졌으며, 대부분의 파이썬 웹 프레임워크가 제공하는 방식보다 개선된 것이다. 인수를 Path, Query 등을 사용하여 함수를 제공할 수 있다.

 

 

2.1.1 본문(Body)

from fastapi import FastAPI, Body

app = FastAPI()

@app.post("/hi)
def greet(who: str = Body(embed=True)):
  return f"Hello, {who}?"

 

  • JSON 형식의 요청 본문에서 who 값을 가져온다는 것을 FastAPI에 알리기 위해 Body(embed=True)가 필요하다. 

2.1.2 HTTP 헤더

from fastapi import FastAPI, Header

app = FastAPI()

@app.get("/hi")
def greet(who: str = Header()):
  return f"Hello? {who}?"

 

  • FastAPI는 HTTP 헤더키를 소문자로 변환하고 하이픈(-)을 밑줄(_)로 변환한다.

 

2.1.3 응답유형

FastAPI.responses는 다음과 같다

- JSONResponse(기본값)

- HTMLResponse

- PlainTextResponse

- RedirectResponse

- FileResponse

- StreamingResponse

 

response 클래스에는 일반적으로 다음이 필요하다

- content: 콘텐츠, 문자열 또는 바이트

- media_type: 미디어유형, 문자열 형태의 MIME 유형값

- status_code: 상태코드, HTTP 정수 상태 코드

- headers: 헤더, 문자열로 구성된 dict

 

2.1.4 타입변환

경로함수는 무엇이든 반환할 수 있으며 기본적으로 JSONResponse를 사용하는 FastAPI는 이를 JSON 문자열로 변환해 일치하는 HTTP 응답헤더 Content-Length 및 Content-Type과 함께 반환한다. 여기에는 모든 Pydantic 모델 클래스가 포함된다.

예를 들어 datetime과 같은 일부 데이터 타입이 주어질 때 예외가 발생하는데, FastAPI는 jsonable_encoder()라는 내부함수를 사용해 모든 데이터구조를 JSON과 비슷한 (JSONable) 파이썬 데이터 구조로 변환한 다음 json.dumps()를 호출해 JSON 문자열로 변환한다. 아래는 pytest로 테스트하는 예제이다.

import datetime
import pytest
from fastapi.encoders import jsonable_encoder
import json

@pytest.fixture
def data():
  return datetime.datetime.now()
  
def test_json_dump(data):
  with pytest.raises(Exception):
    _ = json.dumps(data)
    
def test_encoder(data):
  out = jsonable.encoder(data)
  assert out
  json_out = json.dumps(out)
  assert json_out

 

 

2.1.5 모델타입과 response_model

속성이 동일한 클래스가 여러개 있을 수 있다. 사용자 입력용, 출력용, 내부용으로 나뉘는 경우가 있는데, 이같은 변형이 필요한 경우가 있다.

  • 개인 의료 데이터의 비식별화 등 일부 민감한 정보를 출력에서 제거
  • 사용자 입력에 속성을 추가(예: 생성날짜 및 시간)
from datetime import datetime
from pydantic import Basemodel

class TagIn(BaseModel):
  tag: str
  
class Tag(BaseModel):
  tag: str
  created: datetime
  secret: str

class TagOut(Basemodel):
  tag: str
  created: datetime

 

  • FastAPI 경로함수에서 기본 JSON 이외의 데이터 타입을 반환하는 방법은 다양하다. 한가지 방법은 경로 데코레이터에서 응답 모델 인자를 사용해 FastAPI가 다른것을 반환하도록 하는 것이다.
  • FastAPI는 반환한 객체에 있지만 response_model로 지정된 객체에 없는 속성을 모두 제거한다.
# service/tag.py

from datetime import datetime
from model.tag import Tag

def create(tag: Tag) -> Tag:
  return tag

def get(tag_str: str -> Tag:
  return Tag(tag=tag_str, created=datetime.utcnow(), secret="")

 

  • 여기서 중요한것은 하단의 get_one() 경로함수와 그 경로 데코레이터의 response_model=TagOut이다. 이 함수는 자동으로 내부 Tag 객체를 필터링한 TagOut 객체로 변경한다.
# web/tag.py

from datetime import datetime
from model.tag import TagIn, Tag, TagOut
import service.tag as service
from fastapi import FastAPI

app = FastAPI()

@app.post('/')
def create(tag_in: TagIn) -> TagIn:
  tag: Tag = Tag(tag=tag_in.tag, created=datetime.utcnow(),
    secret="shhhh")
  service.create(tag)
  return tag_in
  
@app.get('/{tag_str}', response_model=TagOut)
def get_one(tag_str: str) -> TagOut:
  tag: Tag = service.get(tag.str)
  return tag

 

3. Starlette과 비동기, 동시성

3.1 Starette

Stareltte는 최신 파이썬 비동기 웹 표준인 ASGI를 지원한다. 대부분의 파이썬 웹 프레임워크는 전통적인 동기식 WSGI 표준을 기반으로 했다. 따라서 Starlette는 Go와 Node.js와 같은 속도를 가능케 해 FastAPI 성능의 기반이 된다.

 

3.2 동시성 유형

3.2.1 분산과 병렬 컴퓨팅

아주 큰 애플리케이션이 있다면, 여러 조각으로 나눠 각 조각을 단일 머신이나 여러 머신의 개별 CPU에서 실행하도록 할 수 있다. 당연히 여러 조각을 관리하는 것이 더 어렵다.

 

3.2.2 운영체제 프로세스

운영체제(OS)는 메모리, CPU, 네트워크 등의 자원사용을 조율한다. 모든 프로그램은 코드를 하나 이상의 프로세스에서 실행한다. OS는 각 프로세스의 CPU 사용시점을 비롯해 자원 접근을 제한해 관리한다. 대부분 선점형 프로세스 스케줄링을 사용해 특정 프로세스가 CPU,  메모리, 기타 자원을 독점하지 못하게 한다. OS는 주어진 설정에 따라 프로세스를 중지하거나 재시작한다.

 

3.2.3 운영체제 스레드

단일 프로세스에서 여러개의 스레드를 제어할 수도 있다. 파이썬의 threading모듈이 이를 관리한다. 프로그램이 I/O에 바인딩되면 스레드를 CPU에 바인딩되면 다중프로세스를 사용하기를 추천하는데, 파이썬은 프로세스기반, 스레드기반 라이브러리를 분리해왔다. 하지만 최신 비동기 함수로 스레드의 이점을 손쉽게 취할 수 있다. FastAPI는 스레드풀을 통해 async def가 아닌 def로 시작하는 일반동기 함수에 대한 스레드도 관리한다.

 

3.2.4 콜백

함수를 작성해 특정 이벤트에 연결하는데, 가장 유명한 라이브러리는 콜백 기반으로 구현한 트위스티드(twisted)이다.

 

3.2.5 파이썬 제너레이터

대부분의 언어처럼 파이썬도 코드를 순차적으로 실행하는데, 함수를 호출하면 파이썬은 코드를 첫줄부터 끝줄이나, return이 나올 때까지 실행한다. 하지만 제너레이터함수는 원하는 곳에서 멈추고 어느 곳에서든 return을 하거나 yield 키워드를 사용해 다시 그 지점으로 돌아갈 수 있다. yield 키워드를 가진 함수는 모두 제너레이터 함수이다.

 

3.2.6 파이썬의 async, await, asyncio

파이썬 3.7버전 이상에서는 asyncio의 async와 await을 예약어로 지정한다. def 키워드 앞에 async가 있으면 그 함수는 대기하게 된다. 함수를 선언할 때 async def를 사용하면 이를 호출하는 쪽에서는 호출할 때 await을 추가해야 한다. 그리고 호출하는 코드가 포함된 함수 자체도 async def로 선언해야하며, 이를 또 호출하는 쪽도 당연히 await를 추가해야한다. 

 

3.3 FastAPI와 Async

웹 서버는 대기에 소모하는 시간이 길어 이런 대기를 피하면(즉, 동시성을 갖춘다면) 성능을 향상할 수 있다. 다른 웹 서버는 스레드나 gevent 등 앞서 언급한 다양한 방법을 사용한다. FastAPI가 파이썬 웹 프레임워크 중 상당히 빠른 이유는 Starlette 패키지가 ASGI를 지원하고 자체 개발한 몇가지 기능을 통해 비동기코드를 통합했기 때문이다.

async와 await를 단독으로 사용한다고 코드가 더 빠르게 실행되는 것이 아니라 오히려 비동기를 설정하는 오버헤드 때문에 더 느려진다. 비동기는 주로 I/O를 오래 기다리지 않기 위해 사용하는 것이다.

 

from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get("/Hi")
async def greet():
  await asyncio.sleep(1)
  return "Hello? World"

 

FastAPI는 URL /hi에 대한 GET 요청을 받으면 비동기 greet() 경로 함수를 호출한다. 보다시피 async def가 정의된 함수를 호출할 때는 호출전에 await을 추가해야 한다. FastAPI는 비동기 경로 함수를 조율하는 비동기 이벤트루프와 동기 경로함수를 위한 스레드풀을 가지고 있다.

 

4. Pydantic과 타입힌트, 모델

Pydantic은 파이썬의 타입 힌트를 사용한 데이터 유효성 검사와 설정관리를 한다. 빠르고 확장가능한 특성을 지니고 있어 대부분 사용한다.

 

4.1 타입힌트

프로그래밍 언어에서 변수가 메모리에 있는 값을 직접 가리키는데, 이를 위해서 값의 크기와 비트를 결정할 수 있도록 타입을 선언해야 한다. 파이썬에서 변수는 객체와 연관된 이름일 뿐이며 타입을 갖는 것은 객체이다. 파이썬은 표준 타이핑 모듈에 타입 힌트를 추가했는데, 파이썬 인터프리터는 프로그램을 실행할 때 타입 힌트 구분을 무시하고 마치 존재하지 않는 듯 동작한다.

변수를 어떤 줄에서 문자열로 처리했다가 다른 타입의 객체를 할당할 수 있는데, 다른 언어의 컴파일러라면 이를 잡아내지만 파이썬은 일반적인 구문 오류와 런타임 오류는 잡아내지만 변수에 대한 혼합 타입은 오류로 처리하지 않는다. 대신, mypy 같은 보조 도구는 타입 힌트에 주의를 기울여 타입 불일치 시 경고를 표시한다.

 

# dictionary를 Pydantic으로 정의하는 예시
from typing import Union
responses: dict[str, Union[str, int]] = {"Marco": "Polo", "answer": 42}

# 파이썬 3.10버전 이상에서 예시
responses: dict[str, str | int] = {"Marco": "Polo", "answer": 42}

 

함수의 반환값에 대한 타입 힌트에서는 콜론 대신 화살표를 사용한다.

function(args) -> type:

# 파이단틱을 사용한 함수 반환값 예시
def get_thing() => str:
  return "yeti"

 

 

4.2 데이터 그룹화

  • dataclasses : 3.7버전에서 도입된 기능으로 클래스 정의 만으로 여러 보일러플레이트 코드를 생성해주는 기능이며, self를 자꾸 사용해야 할 떄 유용함. 주로 순수 데이터 객체를 정의할 때 사용되며, __init__, __repr__, __eq__ 등 메서드를 자동으로 만들어준다.
  • dictionary : 파이썬에서 데이터를 다루기 위한 내장 자료구조이며, 키가 선택사항인지, 키가 누락된 경우 기본값이 있는지 등의 여러 고려사항이 충족된 경우 쓸만하다.

Pydantic은 유효성 검사에 탁월하고 FastAPI와 통합되어 잠재적 데이터 오류를 많이 잡아낸다. 대신, 상속(BaseModel 클래스를 상속)에 의존하는 반면 attrs, Dataclasses는 파이썬 데코레이터를 사용해 객체를 정의한다.

# 동물 모델 정의: model.py
from pydantic import BaseModel

class Animal(BaseModel):
  name: str
  type: str
  area: str
  
thing = Anmimal(
  name: "gorani"
  type: "mammalia"
  area: "korea"

 

  • Animal 클래스는 Pydantic의 BaseModel을 상속한다. 각 필드가 파이썬 문자열임을 나타내는 타입힌트이고 필수값으로 되어있지만, Pydantic의 타입 설명에 Optional이 있다면 nullable로 처리할 수 있다.
  • Pydantic은 타입/값 유효성검사를 모두 자동화할 수 있고 모델이 주고받을 데이터를 정의하기에 좋은 방법이다.

 

4.3 의존성

의존성이란 어떤 시점에 필요한 특정 정보를 말한다. 대개는 정보가 필요한 시점에 바로 가져오는 코드를 작성하지만 HTTP 요청에서 입력값 저장, 유효성검사, 엔드포인트에서 사용자 권한 확인, DB 데이터 조회, Metric(지표)이나 로그 내보내기 등을 수행할 때 함수에서 정보를 필요에 따라 뽑아낸다. 공통 로직을 재사용하여 코드의 결합도를 낮출 수 있으며, db연결을 공유하는 경우나, 보안/인증/역할 요구사항 등을 강제하는 경우에 주로 사용한다.

 

의존성 주입이란 필요한 특정 정보를 함수에 전달하는 것으로, 생각보다 간단하다. 전통적인 방법은 helper function을 전달한 다음 이를 호출해 특정 데이터를 가져오는 것이다. 함수의 인자로 의존성을 정의하면 FastAPI에 의해 자동으로 호출되고 결과로 반환되는 값을 인자에 전달한다. 웹 핸들러 함수(경로함수)는 의존성을 직접 호출하는 것이 아니라 핸들러 함수가 호출될 때 의존성이 처리되는 것이다. 

 

from fastapi import FastAPI, Depends, Query

app = FastAPI()

#의존성 함수:
def user_dep(name: str = Query(....), gender: str = Query(....)):
  return {"name" name, "vilid" True}
  
#경로함수:
@app.get("/user")
def get_user(user: dict = Depends(user_dep)) -> dict:
  return user

 

의존성함수는 FastAPI의 경로함수처럼 동작하지만 선언부에 데코레이터가 없다. 따라서 엔드포인트가 아닌 헬퍼함수인 것이다. 경로 함수는 user라는 인자가 필요하고 의존성함수 user_dep()에서 해당 값을 가져온다고 명시하고 있다.

 

출처: 처음 시작하는 FastAPI
https://ebook-product.kyobobook.co.kr/dig/epd/ebook/4801169212640?LINK=GOO

 

처음 시작하는 FastAPI | 빌 루바노빅

모두 원한 모던한 파이썬 웹 프레임워크의 등장! 빠르게 배우는 FastAPI FastAPI는 간단한 문법으로 빠른 웹 서비스를 만드는 프레임워크를 표방하며 등장한 후, 파이썬 웹 프레임워크의 선두주자인

ebook-product.kyobobook.co.kr

 

'내용 복습 > python' 카테고리의 다른 글

FastAPI 공부 4일차  (0) 2025.05.19
FastAPI 공부 3일차  (0) 2025.05.18
FastAPI 공부 2일차  (0) 2025.04.26
데이터 공부 with 파이썬 2일차  (1) 2025.02.08
데이터분석 with 파이썬 1  (0) 2025.02.06