FastAPI 1~5일차 이론

김기훈·2025년 10월 13일

FastAPI

목록 보기
3/7

1일차

pip

  • pip freeze : 현재 설치된 모든 패키지와 버전을 한 줄씩 출력
    • Flask==3.0.3 이런식으로 보여주기 때문에 어떤 패키지가 설치돼 있는지 알 수 있음
  • pip freeze > requirements.txt
    • 협업이나 배포할 때, 동일한 환경을 재현하기 위해서 사용
      • requirements.txt 안에 현재 버전 정보가 저장
      • pip install -r requirements.txt 이 방식으로 파일을 받아서 똑같은 환경 만듬
  • 개발 환경마다 같은 패키지여도 최신버전을 받을때마다 동작이 바뀔수 있는데
    • pip freeze로 현재 잘 돌아가는 버전을 기록해두면 프로젝트시에 문제를 줄일 수 있음

Poetry

설치 : curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5

  • 매번 pip freeze 할 필요 없이, 알아서 poetry.lock로 종속성을 관리
  • pyproject.toml 로 프로젝트를 관리하는 것을 도와줌
    • black, isort, mypy, ruff, coverage, pytest 설정을 pyproject.toml 한 곳에 모음
  • pip 와 달리 종속성을 group 에 따라 나눌 수 있다.
    • 개발에만 필요한 종속성 : black,pytest
    • 실제 배포시에 필요한 종속성 : fastapi

코드 포메터

  • 프로젝트시에 개발자들끼리 일관된 코드스타일을 유지하도록 도와주는 것

Black


ruff (formatter + linter)

  • 설치: poetry add --group=dev ruff==0.8.2

  • 코드 스타일 검사, 사용하지 않는 import/변수 탐지, 잠재적 버그 패턴 잡기

    • linter : 소스코드를 읽어들인 후 잘못 쓰여진 부분을 자동으로 고쳐주거나
      • 고칠 수 없을때에는 사용자에게 경고를 해주는 툴 (정적 검사)
    • ruff formatter 는 black의 일부기능을 대체할 수 있다.


isort

  • 설치: poetry add --group dev isort

  • Python 코드에서 import 구문을 자동으로 정렬해주는 코드 포매터(tool)

    • 보통 black, ruff, flake8 같은 도구와 함께 코드 스타일을 일관성 있게 유지하기 위해 사용됨
  • 핵심 기능

    • 자동 정렬: import 문들을 알파벳순, 그룹별로 정렬
      • import os, sys → import os import sys
    • 그룹 구분: 표준 라이브러리 / 서드파티 / 로컬 모듈 등을 구분
      • 표준 → 외부 → 로컬 순으로 자동 정렬
    • 자동 병합: 같은 모듈에서 여러 항목을 가져오는 경우 합침
      • from os import path + from os import getcwd = from os import getcwd, path
    • 자동 정리: 사용되지 않는 import 제거는 안 하지만, 위치 정렬만 담당
  • 사용법

    • 현재 프로젝트 전체 정렬: poetry run isort .
    • 특정 파일만 정렬: poetry run isort app/main.py
    • 변경점 미리 보기 (파일은 수정 안 함): poetry run isort . --diff
    • CI용 검사 (정렬 안 되어 있으면 실패): poetry run isort . --check-only

Mypy

  • 설치: poetry add --group=dev mypy==1.13.0

  • 공식 문서: https://github.com/python/mypy

  • Python 코드의 타입 힌트(type hint)를 검사해서 실행 전에 타입 오류를 미리 찾아주는 도구

    • FastAPI는 Pydantic과 타입 힌트를 적극적으로 활용
    • 런타임(runtime)이 아니라 정적(static) 단계에서 타입을 검사
  • 옵션

    • --strict : 엄격한 검사 모드 (가장 많이 사용)
    • --ignore-missing-imports : 외부 패키지 타입 정보가 없어도 무시
    • --disallow-untyped-defs : 타입 힌트가 없는 함수 정의 금지

2일차

pytest

  • 설치: poetry add --group=dev pytest==8.3.4

  • Python에서 단위 테스트(unit test)를 쉽게 작성하고 실행할 수 있게 해주는 도구

    • 복잡한 클래스나 API를 만들 때 함수별로 정상 작동하는지 자동으로 확인 가능
    • 단위테스트: 작성한 코드가 예상한 대로 동작하는지 검증
      • 단위 테스트를 실행하는 도중에 한 번도 예상하지 못한 에러가 발생하지 않았다면 테스트는 성공
      • 반대로 예상치 못한 에러가 발생했다면 테스트는 실패
  • 자주 사용하는 명령어

    • pytest : 전체 테스트 실행
    • pytest tests/ : 특정 폴더만 테스트
    • pytest -v : 테스트 이름과 결과 자세히 표시
    • pytest -x : 첫 실패 시 멈춤
    • pytest --maxfail=3 : 3개 실패 후 멈춤

pytest_asyncio

  • 설치: poetry add --group=dev pytest-asyncio==0.25.0

  • pytest-asyncio 플러그인을 사용하면 async 함수도 실행할 수 있음


Coverage

  • 설치: poetry add --group=dev coverage==7.6.9

  • 프로그램의 코드 중 테스트로 검증된 부분의 비율을 의미

    • 단위 테스트를 전부 실행해도 한 번도 실행되지 않은 코드는 곧 테스트를 안한 코드
    • 커버리지 계산을 통해 “지금 내가 수정한 코드가 제대로 테스트 되고 있는가” 를 판단할 필요가 있음
  • 사용법

    • 단독 사용: coverage run -m pytest
      • 테스트 실행 후 결과 확인: coverage report
        • HTML 리포트 생성: coverage html

Dependency

구분의미예시
Dependency (의존성)실제 코드가 실행될 때 필요한 라이브러리fastapi, uvicorn, sqlalchemy
Dev Dependency (개발 의존성)테스트, 포매팅, 타입체크 등 개발 과정에서만 필요한 라이브러리pytest, mypy, black, isort, coverage
  • 실제 서버(프로덕션)에는 불필요한 개발 도구가 포함되지 않아야 하기 때문에 구분이 필요
    • ex: pytest, mypy, black은 서버에서 실행할 일이 없음
      • 그런데 pip install 시 이런 개발 도구까지 포함되면
        • 용량 증가 / 배포 속도 느려짐 / 보안상 불필요한 의존성 생성
  • 일반 의존성 설치
    • poetry add fastapi uvicorn
      • pyproject.toml의 [tool.poetry.dependencies] 섹션에 추가
  • 개발 의존성 설치
    • poetry add --dev pytest mypy black isort
      • pyproject.toml의 [tool.poetry.group.dev.dependencies] 섹션에 추가됨

자동화 테스트

  • 사람이 직접 테스트하지 않아도, 코드가 스스로 테스트를 수행하도록 만든 시스템
    • 테스트 스크립트나 도구(Pytest 등)가 자동으로 검증하는 과정

자동화 테스트의 종류

종류설명예시 도구
단위 테스트 (Unit Test)함수나 클래스 단위로 작동 검증pytest, unittest
통합 테스트 (Integration Test)여러 모듈이 함께 잘 작동하는지pytest, requests
엔드투엔드 테스트 (E2E Test)실제 사용자 시나리오처럼 전체 동작 검증Selenium, Playwright
CI 자동화 테스트GitHub Actions, GitLab CI 등에서 푸시 시 자동 수행pytest + coverage + mypy 조합

쉘(Shell)

  • 운영체제(OS)에서 명령어를 해석하고 실행해주는 프로그램
쉘 종류설명기본 위치
bash (Bourne Again Shell)가장 일반적 (리눅스, 맥 기본)/bin/bash
zsh (Z Shell)macOS 기본 쉘 (Catalina 이후)/bin/zsh
sh (Bourne Shell)오래된 기본 쉘/bin/sh

Shell Script(.sh)

  • 프로그래밍 언어라기보단, 운영체제(특히 Linux, macOS)의 명령어들을 자동으로 실행시키는 스크립트 파일
    • 즉, 터미널에서 한 줄씩 치는 명령들을 파일로 묶어서 한 번에 자동 실행할 수 있게 만든 것
문법설명예시
#주석# 이건 주석
echo출력echo "Hello"
$(명령)명령 실행 후 결과를 변수처럼 사용echo $(date)
VAR=value변수 선언NAME="KIHOON"
$VAR변수 참조echo $NAME
if, else, fi조건문if [ $a -gt 10 ]; then ... fi
for, done반복문for i in {1..5}; do echo $i; done

Github action

  • GitHub 안에서 자동으로 실행되는 작업(workflow)을 정의하는 CI/CD 도구

    • 코드를 push 하거나 PR을 열었을 때 테스트, 빌드, 배포를 자동으로 수행할 수 있게 해주는 자동화 시스템
    • 개발자가 직접 명령하지 않아도,
    • 테스트 실행 / 코드 포매터 검사 / 린트(lint) 검사 / 배포(ex: AWS, Vercel, Docker 등) 처리
  • 동작 구조 (핵심 개념 3가지)

    • Workflow (워크플로우)
      • 자동화의 “전체 과정”, .github/workflows/ 폴더에 .yml 파일로 정의됨
    • Job (작업)
      • Workflow 안에서 병렬 또는 순차로 수행되는 “작업 단위”
    • Step (단계)
      • 각 Job 안에서 실제 실행되는 명령어 (예: pip install, pytest)

기본 구조 예시

# .github/workflows/test.yml
name: Run tests

on:
  push:             # 코드를 push할 때 실행
    branches: [main]
  pull_request:     # PR이 열릴 때도 실행
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest   # 실행 환경 (가상머신)
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          pip install poetry
          poetry install

      - name: Run tests with pytest
        run: |
          poetry run pytest --cov=app --cov-report=term-missing
  • 작동 예시
    • 코드 push할 때마다 GitHub 서버가 Ubuntu 환경을 띄우고 pytest 테스트 자동 실행 후
      • 결과를 GitHub PR 화면에서 바로 확인할 수 있다.

on: 트리거 (언제 실행되는가)

CI/CD ( GitHub Actions는 이 둘을 다 지원 )

  • CI (Continuous Integration): 코드를 병합할 때마다 자동으로 빌드·테스트
  • CD (Continuous Deployment): 테스트 통과 시 자동으로 배포까지 수행

자주 쓰는 Actions 예시

액션 이름기능설명
actions/checkout코드 가져오기리포지토리 코드 복제
actions/setup-python파이썬 버전 설정Python 환경 자동 설치
actions/cache캐시 저장pip, poetry 등 설치 속도 향상
codecov/codecov-action테스트 커버리지 업로드Coverage 결과 시각화
appleboy/scp-action서버 배포SSH로 파일 전송
docker/login-action도커 로그인Docker Hub 배포용

spec API

  • API가 어떤 요청(request)을 받고, 어떤 응답(response)을 반환하는지 정의한 문서나 데이터 구조
    • 엔드포인트(주소) / 요청 방식(GET, POST 등) / 요청 데이터 형식 / 응답 데이터 구조 / 에러코드
      • 어떤 URL(path) 에 어떤 HTTP 메서드(GET, POST, PUT, DELETE) 를 써야 하는지,
      • 요청(Request) 시 어떤 데이터(body, query, header) 를 보내야 하는지,
      • 응답(Response)이 어떤 형식(JSON, 상태코드, 필드 구조) 으로 오는지를 정리한 문서.
    • ex. FastAPI, Swagger(OpenAPI), Postman 같은 곳에서 보는 “API 문서(JSON 형태의 스펙)

스펙이 필요한 이유

  • 일관성: API 설계가 사람마다 달라지지 않도록 표준화
  • 소통: 프론트엔드, 백엔드, 모바일 팀이 같은 규격으로 협업
  • 자동화: 문서, 테스트, 클라이언트 코드 자동 생성 가능
  • 버전 관리: /v1, /v2 같은 API 변경 사항 추적
  • 품질 관리: API 변경 시 자동 검증 가능 (테스트 자동화 연계)

대표적인 API Spec 표준

표준설명특징
OpenAPI (Swagger)가장 널리 사용되는 REST API 명세JSON/YAML 형식, 자동 문서화 가능
AsyncAPI메시지 기반 시스템 (Kafka, MQTT 등) 명세비동기 통신용
GraphQL SDLGraphQL 스키마 정의 언어쿼리 구조 중심
gRPC ProtoBufgRPC 서비스 명세용고성능, 정적 타입 기반

FastAPI에서의 Spec 구조

  • FastAPI는 내부적으로 아래 단계를 자동 수행
      1. Pydantic 모델 → 요청/응답 데이터 구조 정의
        1. 경로/메서드 데코레이터 → /users, /items 등 명세화
          1. Swagger/OpenAPI JSON 생성 → /openapi.json
            1. Swagger UI 표시 → /docs
              1. ReDoc 표시 → /redoc
  • 결론: FastAPI의 @app.get(), @app.post()에 작성된 정보들이 자동으로 API 스펙으로 변환

“Spec API”가 활용되는 곳

사용처설명
Swagger UI/docs 페이지에서 스펙을 시각적으로 보여줌
Postman ImportOpenAPI JSON을 불러와 자동 테스트 생성
Front-end TypeScript 코드 생성기OpenAPI → Axios API client 자동 생성
CI/CD 파이프라인스펙 변경 시 자동 검증 가능
문서 자동 배포GitHub Pages, ReDocly 같은 문서 사이트로 바로 변환 가능

3일차

써드 파티 라이브러리(Third-Party Library)

  • First-party (1st-party) : 내가 직접 만든 코드 (자체 코드)
    • app.models.user, app.main
  • Second-party (2nd-party) : 내가 사용하는 공식 프레임워크나 SDK (예: FastAPI, Django)
    • os, math, datetime, json
  • Third-party (3rd-party) : 공식이 아닌 외부 개발자가 만든 추가 라이브러리
    • fastapi, requests, sqlalchemy, pydantic, pytest, black
역할써드 파티 라이브러리설명
웹 프레임워크fastapiAPI 서버
비동기 서버uvicornASGI 실행기
ORMsqlalchemy, tortoise-ormDB 모델 관리
타입 검사mypy정적 타입 검사
코드 포매터black, isort코드 정렬 및 스타일링
테스트pytest, coverage자동화 테스트
환경 설정python-dotenv, pydantic-settings.env 파일 관리

uvicorn

  • FastAPI 서버 실행기(엔진)
    • FastAPI는 웹 프레임워크일 뿐, 직접 실행할 수 있는 게 아님.
      • ASGI 서버(uvicorn) 위에서 돌아가야 요청을 처리할 수 있다.
  • uvicorn main:app --reload
    • main:app : main.py 안에 있는 app = FastAPI() 객체를 실행한다는 뜻
    • --reload : 코드 수정하면 서버 자동으로 다시 시작 (개발용 필수)
  • 동작 흐름
    • uvicorn이 켜진다 → FastAPI 앱을 불러온다 → 클라이언트가 http://localhost:8000/items/1 요청 → uvicorn이 요청을 받아서 → FastAPI 함수(@app.get)로 전달 → FastAPI가 응답을 만들어서 → 다시 uvicorn이 클라이언트에게 반환

직렬화 / 역직렬화

  • 서버와 클라이언트는 JSON, XML 같은 텍스트 형식으로 주고받음 : 직렬화 없으면 주고받기 X

    • 파일 저장: 메모리 안 객체는 그냥 저장 불가능 → 직렬화해서 파일에 씀.
    • 캐싱/DB 저장: 객체 그대로 저장 불가 → 직렬화 필요.
  • 직렬화(Serialization)

    • 메모리에 있는 객체(파이썬 dict, list, 클래스 인스턴스 등)저장하거나 전송할 수 있는 형식(문자열, 바이트)으로 바꾸는 과정
      • Python의 dict → JSON 문자열
      • Python 객체 → 바이트 스트림
      • 메모리 안 자료 → 파일로 저장 / 네트워크로 전송 가능
    • “컴퓨터 안 복잡한 구조 → 단순한 문자열/바이트” 로 바꾸는 것
  • 역직렬화(Deserialization)

    • 저장된 JSON 문자열이나 바이트 데이터를 다시 원래 Python 객체로 되돌리는 과정

Squids

  • from sqids import Sqids
  • 비교적 최근에 널리 알려진 URL-안전하고 짧은 고유 ID를 생성해주는 도구
    • 숫자(0 이상의 정수)를 짧고 유니크한 문자열 식별자로 변환하고, 다시 되돌릴 수 있게 설계된 라이브러리
    • URL에 문제가 없는 문자만 사용하며, 욕설이나 부적절한 단어가 포함되지 않도록 블록리스트(blocklist) 기능을 제공
    • 여러 언어(40개 이상)로 포팅되어 있어, 백엔드 · 프론트엔드 등 다양한 스택에서 동일한 방식으로 사용 가능

주요 특징 및 동작 원리

기능설명
Encode / Decode숫자 혹은 숫자 배열 → 문자열 ID로 변환, 다시 역변환 가능
블록리스트(blocklist)미리 정의된 부적절한 단어가 ID에 포함되지 않도록 필터링
최소 길이(min length)생성되는 ID가 적어도 설정한 길이 이상이 되도록 보장
사용자 정의 알파벳기본 문자집합 외에 다른 문자 집합을 사용해서 ID 생성 가능
비암호화 방식Sqids는 암호화(encryption)가 아니며 정보 은폐용 설계는 아니에요 — 즉, 완전히 숨기려는 데이터에는 적합하지 않음
비순차 출력연속 숫자를 인코딩해도 결과가 순차적으로 보이지 않게 설계 가능

장점 및 주의사항

  • 장점
    • 짧고 읽기 좋은 ID 생성 → URL이나 공유 링크 등에 유용
    • 폭넓은 언어 지원 → 여러 언어에서 동일한 로직 사용 가능
    • 미니멀 라이브러리 → 비교적 가벼운 의존성
  • 주의사항
    • 보안 목적 아님: 중요한 비밀 데이터나 키 숨기기 용도로 쓰면 안좋음
    • ID 충돌 가능성: 역변환 후 다시 인코딩했을 때 같은 문자열이 나오지 않을 수도 있다는 조건이 있을 수 있음
    • 블록리스트 부담: 필터링해야 할 단어가 많거나, 문자 집합이 작으면 재생성 실패 가능성이 증가
    • 알파벳 노출 우려: 만약 누군가 알파벳 설정을 유추하면 인코딩 방식이 유추 가능
    • 숫자 범위 제한: 음수는 인코딩할 수 없고, 숫자가 매우 클 경우 일부 구현에서 제한이 존재 가능

orjson

  • 설치: poetry add orjson
  • Python에서 JSON 직렬화(serialization)와 역직렬화(deserialization)를 “매우 빠르게” 수행
    • Python 내장 json 모듈보다 훨씬 빠르고, FastAPI, Pydantic, Dataclasses 같은 Python 객체도 자동 변환
  • 주의 사항
    • orjson.dumps()는 문자열이 아니라 bytes를 반환
    • 문자열이 필요한 경우: orjson.dumps(data).decode()

사용 예시

  • 응답 변환 시 json.dumps() 대신 orjson.dumps() 사용

고급 옵션(option 파라미터)

  • orjson.dumps()는 여러 옵션을 조합해서 사용 가능
옵션설명
orjson.OPT_INDENT_2보기 좋게 들여쓰기 (개발용)
orjson.OPT_SORT_KEYS키 이름을 정렬해서 출력
orjson.OPT_NAIVE_UTCtimezone 없는 datetime을 UTC 기준으로 직렬화
orjson.OPT_SERIALIZE_NUMPYnumpy 배열을 자동 변환
orjson.OPT_PASSTHROUGH_DATACLASSdataclass 객체를 직렬화
orjson.OPT_SERIALIZE_UUIDUUID 객체 지원
orjson.OPT_OMIT_MICROSECONDSdatetime의 마이크로초 제거

Final

  • Python의 타입 힌트 시스템(typing 모듈) 안에서 “변경 불가능(재할당 불가)”을 의도적으로 표시하는 기능
    • 즉, 코드의 “불변(immutability)”을 정적 타입 검사기(mypy 등) 에게 알려주는 도구
from typing import Final

PI: Final = 3.14159
PI = 3  # ❌ mypy에서 오류 발생!
  • 실행은 되지만 mypy검사 시
    • error: Cannot assign to final name "PI" 오류 출력
  • 파이썬 자체는 재할당을 허용하지만 타입 검사 도구가 의도된 불변성을 보장하는 것

속성에 사용

from typing import Final

class Config:
    VERSION: Final = "1.0.0"

cfg = Config()
cfg.VERSION = "2.0.0"  # ❌ 타입 검사기에서 오류 발생

메서드에 사용

from typing import final

class Base:
    @final
    def save(self):
        print("Saving...")

class Sub(Base):
    def save(self):  # ❌ mypy 오류: Cannot override final method "save"
        print("Override")
  • 메서드에 @final 데코레이터를 붙이면 상속받은 클래스가 그 메서드를 오버라이드(재정의) 하지 못하게 막음
from pydantic import BaseModel

class Config(BaseModel):
    id: int
    name: str

    class Config:
        frozen = True

cfg = Config(id=1, name="Kihoon")
cfg.name = "Lee"  # ❌ 실제로 에러 발생 (AttributeError)
  • Pydantic에서는 아래처럼 완전히 실행 중에도 불변(frozen) 으로 만들 수 있다.
    • Final은 이런 “실제 제한”이 아니라, “코드 규칙상 금지”

Base62/64

  • 프로그래밍 전반에서 데이터를 “짧고 안전하게 문자로 표현하는 표준 인코딩 방식”으로 아주 많이 사용

Base 인코딩

  • “이진 데이터(0과 1로 된 값)”를 사람이 읽을 수 있는 “문자열” 형태로 변환하는 방식
    • 이미지, 암호화 키, 바이너리 데이터, ID 같은 걸 URL이나 JSON에 안전하게 담을 수 있게 문자로 바꾸는 것이 목적

Base64 — 가장 유명한 표준 인코딩

  • “64개의 문자”만 사용해서 바이너리 데이터를 문자열로 인코딩하는 국제 표준(📜 RFC 4648)
    • A~Z : 26 , a~z : 26 , 0~9 : 10 , + / : 2 , =(패딩용 문자) 총 64개
import base64

text = "kihoon"
encoded = base64.b64encode(text.encode())
print(encoded)  # b'a2lob29u'

decoded = base64.b64decode(encoded)
print(decoded.decode())  # kihoon
  • kihoon → a2lob29u → 바이너리 데이터를 안전한 문자로 바꾼 것

Base62 — “짧고 URL 안전하게” 만든 버전

  • Base64와 거의 비슷하지만, URL이나 파일명에 문제가 될 수 있는 +, /, = 등을 제거하고 총 62개의 문자 (A–Z, a–z, 0–9) 만 사용하는 인코딩
import base62

num = 123456789
encoded = base62.encode(num)
print(encoded)  # 8M0kX

decoded = base62.decode(encoded)
print(decoded)  # 123456789
  • Base62는 보통 숫자를 짧은 문자열로 바꾸는 용도로 사용

디버그 모드

  • 개발 중일 때 자동으로 코드 변경 감지 + 에러 화면 표시를 켜주는 모드
from fastapi import FastAPI

app = FastAPI(debug=True)
  • 이런식으로 하면,
    • 코드 바꾸면 자동으로 서버 재시작
    • 예외 발생 시 브라우저에 상세 에러 트레이스 표시
      • 즉, “개발자용 오류 추적 모드”
      • 배포(Production) 환경에서는 절대 켜두면 안됨
  • “디버깅(debugging)”은 버그를 찾고 고치는 과정이고, “디버그(debug)”는 그 행동을 뜻

디버그(debug)

  • 프로그램의 오류를 찾고 수정하다 (행동)
    • 말할 땐 "디버그"라고 짧게

디버깅(debugging)

  • 오류를 찾고 수정하는 행위 또는 과정 (행위,과정)
    • 문서나 공식 표현에서는 "디버깅" 이라고 사용
  • 코드가 의도대로 동작하지 않을 때, 어디서 문제가 생겼는지 추적하고 해결하는 과정 전체를 말함

4일차

EdgeDB

  • 유명한 파이썬 핵심 컨트리뷰터들이 모여 만든 차세대 데이터베이스
    • 실무에서 자주 사용되지는 않지만 성능이 좋고
      • query language 의 유연함, orm 대신 code generation 등등 장점이 많음
    • Postgresql 위에서 동작
    • 엣지디비 공식 클라이언트로 쿼리를 날리면 자동으로 “객체” 로 결과 리턴
      • 따라서 ORM 을 쓸 필요가 없음
    • 최신데이터베이스로 공식 유튜브 채널, 공식 디스코드 채널, 심지어 카카오톡 오픈채팅 까지 지원

핵심 써드파티 라이브러리

ORM(Object-Relational Mapping)

  • SQL을 직접 쓰지 않고, 파이썬 객체로 데이터베이스를 다루는 기술

Tortoise ORM

  • FastAPI나 Starlette처럼 비동기(async/await) 기반 웹 프레임워크에서 사용하기에 특화된 ORM
from tortoise import Tortoise, fields, models

class User(models.Model):
    id = fields.IntField(pk=True)
    username = fields.CharField(max_length=50)
    is_active = fields.BooleanField(default=True)

# 연결 초기화
await Tortoise.init(
    db_url="sqlite://db.sqlite3",
    modules={"models": ["app.models"]}
)
await Tortoise.generate_schemas()

# 데이터 추가
user = await User.create(username="kihoon")

# 조회
users = await User.all()
print(users)
  • 전부 await 사용 → 비동기 DB 처리
    • 즉, FastAPI의 async def와 자연스럽게 연결

주요 특징

  • 비동기 지원: asyncio 기반 (FastAPI와 궁합 최고)
  • ORM 지원: SQL 없이 .create(), .filter(), .all() 등 사용
  • 관계 지원: ForeignKey, ManyToMany 지원
  • 다중 DB 지원: SQLite, MySQL, PostgreSQL 등

비슷한 라이브러리와 비교

  • SQLAlchemy
    • 동기 + 비동기 지원, 전통적이며 매우 강력함
  • Tortoise ORM
    • 완전 비동기, 가볍고 FastAPI 친화적
  • Django ORM
    • 동기, Django 전용

Cryptography

  • “암호화(encryption)”, “복호화(decryption)”, “해싱”, “디지털 서명” 등을 수행하는 파이썬 공식 암호화 라이브러리
    • ex. 비밀번호를 안전하게 저장, JWT 토큰, 인증 키, HTTPS 통신 등에 사용

주요 기능

  • 해시(hash): 비밀번호나 파일 무결성 확인
  • 암호화(encryption): 데이터 보호 (Fernet 등)
  • 공개키 암호화(RSA): 인증, 서명, HTTPS 통신 등에 사용
  • HMAC, AES 지원: 고급 보안 프로토콜 구현 가능

예시: 해싱 (비밀번호 저장)

from cryptography.hazmat.primitives import hashes

digest = hashes.Hash(hashes.SHA256())
digest.update(b"kihoon123")
hash_value = digest.finalize()
print(hash_value.hex())
# e.g. '4e3f78a6df...'

예시: 대칭키 암호화 (Fernet)

from cryptography.fernet import Fernet

# 키 생성
key = Fernet.generate_key()
cipher = Fernet(key)

# 암호화
token = cipher.encrypt(b"secret message")
print(token)

# 복호화
plain = cipher.decrypt(token)
print(plain)

Migration

  • 데이터베이스 구조를 변경하는 작업 (테이블 생성/삭제, 컬럼 추가 등)

Aerich (실습은 fastapi-4)

  • “Tortoise ORM용 마이그레이션 툴”
    • 마이그레이션(Migration) = 데이터베이스 구조(테이블, 컬럼 등)를 코드 변경에 맞게 자동 업데이트하는 기능
      • ex. 모델에 새로운 필드를 추가하면 → DB 스키마도 자동으로 갱신해야 함 그걸 Aerich가 도움
  • Alembic이 SQLAlchemy 전용이라면, Aerich은 Tortoise ORM 전용 마이그레이션 툴

기본 명령어 흐름

  • aerich init - 마이그레이션 설정 생성
    • aerich init -t app.models.TORTOISE_ORM - 초기화
  • aerich init-db - 첫 DB 생성 및 마이그레이션 폴더 생성
  • aerich migrate - 모델 변경 감지 후 마이그레이션 파일 생성
    • aerich migrate --name "add-user-table" - 변경사항 감지
  • aerich upgrade - DB 변경사항 적용

설정 예시

TORTOISE_ORM = {
    "connections": {"default": "sqlite://db.sqlite3"},
    "apps": {
        "models": {
            "models": ["app.models", "aerich.models"],
            "default_connection": "default",
        },
    },
}

실제 예시

$ aerich migrate --name "add_age_column"
$ aerich upgrade
  • User 모델에 age 필드를 추가했을 때 자동으로 ALTER TABLE user ADD COLUMN age INT; 실행

Alembic

  • Alembic은 보통 터미널 명령어로 사용하는 도구
    • Python 코드 내부에서 import alembic 이 아니라 명령어로 실행하는 게 기본 사용법
  • Alembic은 “SQLAlchemy의 메타데이터”를 이용
    • Alembic은 env.py 내부에서 이미 프로젝트의 메타데이터를 불러와서 자동으로 ORM 모델을 감지하고 비교
  • SQLAlchemy용 데이터베이스 마이그레이션 도구
    • 즉, 데이터베이스 스키마(테이블 구조 등) 가 변경될 때마다 직접 SQL을 작성하지 않아도,
      • Python 코드로 “변경 이력”을 관리하고 자동으로 반영할 수 있게 해주는 툴
  • 작동원리
    • SQLAlchemy 모델 메타데이터를 분석해서 DB 구조를 파악
      • 이전 revision의 구조와 비교하여 차이점(diff)을 계산
        • 그 차이를 Python 코드 형태의 migration 파일로 저장
          • upgrade() 함수가 실행될 때 실제 ALTER TABLE SQL을 실행

역할

  • FastAPI나 Flask에서 SQLAlchemy 모델을 수정하면 구조의 변화 발생
    • 새 컬럼 추가
    • 컬럼 이름 변경
    • 제약조건 변경
    • 테이블 삭제/생성 등
      • 이때 직접 SQL로 ALTER TABLE을 실행하는 건 번거롭고, 버전 관리가 어려움
        • Alembic은 이런 변경을 버전(Revision) 으로 기록해서 자동화

Alembic 기본 구조

📦 project/
 ┣ 📂 alembic/
 ┃ ┣ 📂 versions/       ← 버전별 migration 파일 저장
 ┃ ┗ 📄 env.py          ← 환경 설정 파일
 ┣ 📄 alembic.ini       ← 전체 설정
 ┗ 📄 models.py         ← SQLAlchemy 모델 정의
개념설명
Migration데이터베이스 구조를 변경하는 작업 (테이블 생성/삭제, 컬럼 추가 등)
Revision변경 내역을 하나의 버전으로 저장한 파일 (versions/ 폴더에 있음)
Upgrade / Downgrade스키마를 새로운 버전으로 올리거나(Upgrade), 이전 버전으로 되돌리는(Downgrade) 과정
env.pyAlembic 환경 설정 파일 (DB 연결, 대상 메타데이터 설정 등)
alembic.iniAlembic 전역 설정 파일 (DB URL, 로그 설정 등)

기본 명령어

명령어설명
alembic init alembicAlembic 환경 초기화
alembic revision -m "Add user table"새 migration 파일 생성
alembic revision --autogenerate -m "Add user table"모델 변경사항 자동 감지 후 migration 생성
alembic upgrade head최신 버전까지 DB 스키마 업그레이드
alembic downgrade -1바로 이전 버전으로 되돌림
alembic current현재 DB 스키마 버전 확인
alembic history모든 revision 내역 확인

예시

FastAPI나 Flask에서 자주 쓰는 패턴


Tortoise ORM + conftest.py

  • Tortoise ORM + conftest.py 조합은
    • 테스트 환경에서 비동기 ORM(DB 연결)을 안전하게 초기화하고 정리 하기 위한 핵심 구성

conftest.py

  • pytest는 테스트 실행 시 conftest.py 파일을 자동으로 인식해서 그 안의 공용 fixture들을 테스트 전/후에 실행
    • DB 초기화
    • Tortoise ORM 세팅
    • 더미 데이터 생성
    • 테스트 끝난 뒤 DB 정리
      • 이런 반복 작업을 한 곳(conftest.py) 에서 처리할 수 있게 해주는 구조

Tortoise ORM 테스트 시 문제점

  • Tortoise ORM은 비동기(async) 로 작동
    • 따라서 일반적인 pytest로는 await가 안됨
      • await User.create(username="kim") ❌, await는 async 함수 안에서만 가능
  • 따라서 pytest-asyncio 플러그인 설치필요
    • poetry add -D pytest pytest-asyncio
      - 이제 테스트 함수 안에서 async def를 쓸 수 있음

conftest.py의 핵심 역할

  • init_db
    • 테스트 전 Tortoise ORM을 초기화하고 in-memory SQLite 연결
  • generate_schema
    • 모델 기반으로 임시 DB 테이블 생성
  • finalize_db
    • 테스트 후 연결 닫기 및 정리 (cleanup)

구조 예시

# conftest.py
import pytest
from tortoise import Tortoise

@pytest.fixture(scope="session", autouse=True)
async def init_db():
    """
    모든 테스트가 시작되기 전에 1회 실행되어
    Tortoise ORM 환경을 초기화하고 메모리 DB를 준비함.
    """
    await Tortoise.init(
        db_url="sqlite://:memory:",   # 테스트용 메모리 DB
        modules={"models": ["app.tortoise_models.user_model"]},
    )
    await Tortoise.generate_schemas()  # 테이블 자동 생성

    yield  # 테스트 실행 구간

    await Tortoise.close_connections()  # 테스트 끝나면 연결 닫기
  • @pytest.fixture(scope="session", autouse=True)
    • scope="session": 테스트 세션 전체에서 한 번만 실행 (속도↑)
    • autouse=True: 각 테스트 파일에서 따로 import 안 해도 자동 실행됨
  • await Tortoise.init(...)
    • ORM 연결 설정 (DB URL + 모델 경로)
    • 실제 앱처럼 ORM 환경을 세팅하지만, 테스트용 DB 사용
  • await Tortoise.generate_schemas()
    • 선언된 모델(BaseModel, UserModel 등)을 기반으로 테이블 생성
  • yield
    • 이 위치에서 pytest가 테스트 함수를 실행함
    • 테스트가 끝나면 yield 아래의 정리 코드(close_connections()) 실행

실제 테스트 예시

# test_user.py
import pytest
from app.tortoise_models.user_model import User # class User()

@pytest.mark.asyncio
async def test_user_creation():
    user = await User.create(username="kim", email="test@example.com")
    assert user.id is not None

    fetched = await User.get(username="kim")
    assert fetched.email == "test@example.com"
  • pytest.mark.asyncio 덕분에 async def 테스트가 가능
  • conftest.py 덕분에 테스트 전에 이미 DB가 초기화 되어 있음

FastAPI와 함께 쓰는 경우

  • FastAPI 앱을 함께 테스트한다면 conftest.py는 아래처럼 확장
# conftest.py
import pytest
from httpx import AsyncClient
from tortoise import Tortoise
from app.main import app  # FastAPI 앱 import

@pytest.fixture(scope="session", autouse=True)
async def init_db():
    await Tortoise.init(
        db_url="sqlite://:memory:",
        modules={"models": ["app.tortoise_models.user_model"]},
    )
    await Tortoise.generate_schemas()
    yield
    await Tortoise.close_connections()

@pytest.fixture()
async def client():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        yield ac
# 이제 이렇게 간단하게 사용 가능
@pytest.mark.asyncio
async def test_user_api(client):
    response = await client.post("/users", json={"username": "kim", "email": "a@a.com"})
    assert response.status_code == 201


5일차

dmypy

  • Mypy의 데몬(daemon) 모드 클라이언트
    • Mypy를 더 빠르게 돌리기 위해 백그라운드에서 상시 실행되는 타입 검사 서버를 관리하는 도구
  • dmypy는 백그라운드 데몬 프로세스를 실행해서, 변경된 부분만 다시 검사하게 만들어줌.

Mypy vs dmypy 비교

항목mypydmypy
실행 방식매번 새로 실행백그라운드 데몬이 지속 실행
속도느림 (모든 파일 재검사)빠름 (변경된 부분만 검사)
사용 편의성단순 실행초기 설정 필요
권장 사용소규모 스크립트중·대형 프로젝트

주요 명령어

  • dmypy start
    • 백그라운드 Mypy 서버 시작
  • dmypy run .
    • 현재 디렉터리의 모든 파일을 검사 (데몬이 없다면 자동으로 시작 후 검사)
  • dmypy stop
    • 서버 중지
  • dmypy restart
    • 서버 재시작 (환경이 꼬였을 때 유용)
  • dmypy status
    • 서버가 실행 중인지 확인
  • dmypy check <파일 경로>
    • 특정 파일만 타입 검사

예시 (Poetry + FastAPI 환경 기준)

# bash
poetry add --group dev mypy
poetry run dmypy start
poetry run dmypy run app/

# 결과 예시
Daemon started
Success: no issues found in 10 source files

장점

  • 속도: 변경된 파일만 검사하므로 매우 빠름
  • 안정성: 프로젝트 전체 타입 일관성 유지
  • CI 통합: GitHub Actions 등 자동화 파이프라인에 쉽게 통합 가능
  • 캐싱: 이전 검사 결과를 캐시해서 불필요한 재검사 방지

바다코끼리 연산자(:=)

  • 값을 평가하면서 동시에 변수에 할당: 변수 := 표현식
    • print(x := 10)
      • x에 10을 저장하고 10을 반환해서 출력, 결과 = 10

주의

  • :=은 표현식 내부에서만 사용 가능
    • if, while, list comprehension, lambda 내부 등
  • x := 10만 단독으로 쓰면 SyntaxError
  • 너무 복잡한 표현식에 넣으면 오히려 헷갈림

예시

# while 문
line = input()
while line != "quit":
    print(f"입력: {line}")
    line = input()

# input() 결과를 line에 저장하면서 동시에 비교 조건에 사용.
while (line := input()) != "quit":
    print(f"입력: {line}")

# list comprehension
# 길이가 3 이상인 단어만 리스트로 추출
words = ["a", "bee", "dog", "elephant"]
result = [w for w in words if (n := len(w)) > 2]
print(result)  # ['bee', 'dog', 'elephant']

# if 조건문에서 계산과 저장을 동시에
if (count := len([1, 2, 3, 4])) > 2:
    print(f"{count}개의 항목이 있습니다.")

TDD(Test Driven Development) 구현 방식

  • “테스트를 먼저 작성하고, 테스트를 통과시키는 최소한의 코드를 작성한 뒤, 리팩터링한다.”
    • 실패하는 테스트를 먼저 만들고
      • 그 테스트를 통과시키기 위한 최소 코드를 작성하고
        - 코드를 정리(리팩터링)하는 과정을 반복하는 개발 방식

TDD의 핵심 사이클 — Red → Green → Refactor

단계이름설명
🔴 Red실패하는 테스트 작성기능 요구사항을 기반으로 먼저 테스트를 작성함. 아직 구현이 안 됐기 때문에 반드시 실패해야 함.
🟢 Green테스트 통과시키기 위한 최소한의 코드 작성테스트를 “통과시키는 것”만 목표로 최소한의 코드 작성. (예쁘게 짜지 않아도 됨)
🔵 Refactor코드 리팩터링 (중복 제거, 구조 개선)테스트가 통과하는 상태를 유지하면서, 코드를 개선하고 정리함.

실제 TDD 구현 시 흐름 (FastAPI 예시)

  • 회원가입 API 만들기
    1. Red: “POST /users 요청 시 201 반환해야 한다” 테스트 작성
    • 구현 전이므로 실패
def test_create_user(client):
    response = client.post("/users", json={"username": "kim", "password": "1234"})
    assert response.status_code == 201
    1. Green: FastAPI 엔드포인트 최소 구현 (테스트만 통과하도록 간단히 작성)
@app.post("/users", status_code=201)
def create_user(data: dict):
    return {"id": 1, "username": data["username"]}
    1. Refactor: DB 연동, 모델 추가, 예외 처리, 검증 로직 추가 (테스트는 계속 통과해야 함)
    • 중복 제거, 가독성 향상, 구조화

장점

  • 버그 조기 발견
  • 명세 기반 개발(요구사항 중심)
  • 자동화 테스트 자산 축적
  • 개발 속도 향상 (초기엔 느려도 장기적으로 빠름)

Pattern

구조 예시

app/
├── api/
│   └── user_router.py      # FastAPI 엔드포인트
├── services/
│   └── user_service.py     # 비즈니스 로직 (Service Layer)
├── repositories/
│   └── user_repository.py  # DB 접근 (Repository Layer)
└── models/
    └── user_model.py

- Controller(API):	요청/응답 처리 (입출력 담당) = 요청받음
- Service:	비즈니스 로직 담당 (검증, 계산, 규칙, 정책) = 로직 처리
- Repository:	데이터 저장소 접근 담당 (DB, 외부 API 등) = 데이터 CRUD 수행

Service Design Pattern

  • 비즈니스 로직을 전담하는 계층을 따로 분리하는 설계 방식
    • 즉, “데이터 접근”이나 “라우터(API)”에서 복잡한 로직이 섞이지 않게 하는 것이 핵심

예시 코드

# user_router.py
from fastapi import APIRouter
from app.services.user_service import UserService

router = APIRouter()
service = UserService()

@router.post("/users")
async def create_user(data: dict):
    return await service.create_user(data)
# user_service.py
from app.repositories.user_repository import UserRepository

class UserService:
    def __init__(self):
        self.repo = UserRepository()

    async def create_user(self, data):
        # 비즈니스 로직 예시
        if len(data["password"]) < 6:
            raise ValueError("비밀번호는 6자 이상이어야 합니다.")
        user = await self.repo.insert_user(data)
        return {"message": "회원가입 완료", "user": user}

Repository Design Pattern (리포지토리 패턴)

  • DB 접근 로직을 한 곳에 모아두는 설계 방식
  • ORM(SQLAlchemy, Tortoise 등)을 직접 API 코드에서 쓰지 않고,
    • Repository 클래스가 대신 담당하도록 하는 구조

예시 코드

# user_repository.py
from app.models<.user_model import User

class UserRepository:
    async def insert_user(self, data: dict):
        user = await User.create(**data)
        return user

    async def find_user_by_id(self, user_id: int):
        return await User.get_or_none(id=user_id)

    async def delete_user(self, user_id: int):
        return await User.filter(id=user_id).delete()

# user_service.py (연동 예시)
from app.repositories.user_repository import UserRepository

class UserService:
    def __init__(self):
        self.repo = UserRepository()

    async def get_user(self, user_id: int):
        user = await self.repo.find_user_by_id(user_id)
        if not user:
            raise ValueError("존재하지 않는 사용자입니다.")
        return user

장점

  • DB 독립성 확보
    • ORM 또는 DBMS 교체 시, Repository만 수정
  • 중복 코드 감소
    • CRUD 로직을 일관되게 관리
  • 테스트 용이
    • 가짜 Repository(Mock)로 테스트 가능
  • 유지보수성 향상
    • 데이터 접근 계층을 단일 책임으로 분리

DTO(Data Transfer Object)

  • “계층 간 데이터 전달만 담당하는 객체”
    • DB 모델(Model)이나 API 요청(Request)과는 별개로
      • “필요한 데이터만 깔끔하게 담아서 주고받는 전용 구조체”
  • 목적: 계층 간 안전하고 명확한 데이터 전달
    • 예시: UserCreateDTO, UserResponseDTO, LoginDTO, MovieDetailDTO
  • 기능이 있는 클래스나 도구가 아닌 역할(패턴)을 구분하기 위한 개념적 이름
    • “이 객체는 데이터 전송용으로만 쓸 거야” 라고 약속하기 위해 붙이는 이름

DTO 역할

  • API ↔ Service ↔ Repository 사이에서는 데이터가 오가는데
    • 그냥 딕셔너리나 ORM 모델을 막 넘기면 구조가 뒤죽박죽 복잡해짐
  • 요청 DTO (Request DTO): 클라이언트 → 서버로 들어오는 데이터 구조 정의
  • 응답 DTO (Response DTO): 서버 → 클라이언트로 반환되는 데이터 구조 정의
  • 내부 DTO (Service/Repository 간): 내부 계층 간 안전한 데이터 전달용

예시 (FastAPI 기준)

  • User 생성 API — Request/Response DTO 분리
# schemas/user_dto.py
from pydantic import BaseModel, EmailStr

class UserCreateDTO(BaseModel):  # Request DTO
    username: str
    email: EmailStr
    password: str

class UserResponseDTO(BaseModel):  # Response DTO
    id: int
    username: str
    email: EmailStr
  • API에서 DTO 사용
# user_router.py 
from fastapi import APIRouter
from app.schemas.user_dto import UserCreateDTO, UserResponseDTO
from app.services.user_service import UserService

router = APIRouter()
service = UserService()

@router.post("/users", response_model=UserResponseDTO)
async def create_user(data: UserCreateDTO):
    user = await service.create_user(data)
    return user
  • DTO를 통해 안전하게 전달
# user_service.py
from app.repositories.user_repository import UserRepository
from app.schemas.user_dto import UserCreateDTO, UserResponseDTO

class UserService:
    def __init__(self):
        self.repo = UserRepository()

    async def create_user(self, data: UserCreateDTO) -> UserResponseDTO:
        # 검증 or 비즈니스 로직
        if len(data.password) < 6:
            raise ValueError("비밀번호는 6자 이상이어야 합니다.")

        user = await self.repo.insert_user(data)
        # Repository 결과를 ResponseDTO로 변환
        return UserResponseDTO.model_validate(user)
profile
안녕하세요.

0개의 댓글