습관 만들기
- 시작전에 전체 테스트 한번 돌려보는 습관 기르기

API Spec만들기
API 스펙(spec) : API가 어떻게 동작하는지에 대한 “계약서”(실무에서 구현보다 스펙을 먼저 작성 하는게 좋음)
- 어떤 URL(path) 에 어떤 HTTP 메서드(GET, POST, PUT, DELETE) 를 써야 하는지,
- 요청(Request) 시 어떤 데이터(body, query, header) 를 보내야 하는지,
- 응답(Response)이 어떤 형식(JSON, 상태코드, 필드 구조) 으로 오는지를 정리한 문서.
main 구분



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 객체로 되돌리는 과정
orjson
- orjson은 JSON을 위한 Python용 빠르고 정확한 JSON 라이브러리
- 다른 라이브러리 보다 정확도가 높다
- dataclass , datetime , numpy , UUID 인스턴스를 기본적으로 직렬화
- 설치

orjson 주요 기능

- JSONEncodeError , Type is not JSON serializable: ...
- 지원되지 않는 유형에서 발생: 이 문제를 해결하기 위해서는 default를 지정해야함
fastapi 실습
router 생성


- APIRouter : 라우터 객체
- prefix : URL 앞부분을 미리 정해두는 값
- /v1/edgedb/meetings 아래의 모든 엔드포인트가 이 라우터에 속함
tags=["Meeting"]
- Swagger 문서에서 이 API 그룹을 "Meeting" 이라는 이름으로 묶어 보여줌


DTO
- data를 전달하기 위한 목적으로 생성한 객체를 의미
- dto 는 오직 데이터를 “전달”만 해야 하며, 데이터를 수정, 추가, 삭제하면 안됨
- pydantic basemodel 을 상속하는 CreateMeetingResponse 가 현재 dto
DTO 대신 Dict를 사용

- dict일 경우 실수로 키를 추가하거나 누락해도 오류를 잡아내기 쉽지 않음
식별자
Final
- 재할당을 막고 mutable한걸 immutable로 변경시키지는 못한다.


Base62/64
import string
from typing import Final, ClassVar
class Base62:
BASE: Final[ClassVar[str]] = string.ascii_letters + string.digits
BASE_LEN: Final[ClassVar[int]] = len(BASE)
@classmethod
def encode(cls, num: int) -> str:
if num < 0:
raise ValueError(f"{cls}.encode() needs positive integer but you passed: {num}")
if num == 0:
return cls.BASE[0]
result = []
while num:
num, remainder = divmod(num, cls.BASE_LEN)
result.append(cls.BASE[remainder])
return "".join(result)
class Base62:
BASE: Final[ClassVar[str]] = string.ascii_letters + string.digits
BASE_LEN: Final[ClassVar[int]] = len(BASE)
- BASE : a~z,A~Z,0~9 : 62개 이기 때문에 Base62
ClassVar[str] : 타입 힌트중의 하나로 클래스 변수임을 명시하기 위하여 사용
- 타입 힌트는 없어도 상관은 없지만 인스턴스 변수로 오해받을 수 있다.
- str로 이 값의 타입은 문자열이라는것을 알린다.
- mypy같은 타입체커가 ClassVar을 보고 이건 클래스 변수니까 self로 접근하지 마라 라고 알려줌
- Final : 재할당 금지
@classmethod
def encode(cls, num: int) -> str:
- @classmethod = 데코레이터 : 클래스 메서드를 정의할 때 사용
- 첫 번째 인자가 cls (즉, 클래스 자체).
- 클래스 변수(BASE, BASE_LEN처럼 ClassVar)에 접근가능
- 인스턴스를 만들지 않고도 클래스명.method() 형태로 호출 가능.
- Base62.encode(12345) 처럼 바로 호출 가능.
- 데코레이터가 없었으면 cls가 전부 self로 바뀜
- encoder = Base62() 이렇게 인스턴스 생성하고 print(encoder.encode(12345)) 이렇게 사용
if num < 0:
raise ValueError(f"{cls}.encode() needs positive integer but you passed: {num}")
if num == 0:
return cls.BASE[0]
- num < 0 → 예외 발생시킴. (음수는 인코딩 불가) / num == 0 →
BASE[0] 반환 (즉 'a')
result = []
while num:
num, remainder = divmod(num, cls.BASE_LEN)
result.append(cls.BASE[remainder])
return "".join(result)
- while num: : num이 0이 아닐 동안 반복한다.(사실상 while num != 0: 과 같음)
- divmod(num, cls.BASE_LEN) : num을 62로 나눈 몫(num)과 나머지(remainder)를 동시에 구함.
- Base62.encode(62) 일 경우
- 1바퀴: num, remainder = divmod(62, 62) (1,0) → 'a' 추가
- 2바퀴: num, remainder = divmod(1, 62) (0,1) → 'b' 추가
디버깅
프로그램에서 발생하는 오류(bug)를 찾아내고, 그 원인을 파악해서 수정하는 과정




Deterministic
Base62 연산은 “결정적”(deterministic) / 결정적: 같은 입력을 하면 항상 같은 결과가 나온다는 뜻
sqids
Sqids는 숫자로부터 짧은 고유 식별자를 생성할 수 있는 오픈 소스 라이브러리