fastapi - G6 간단하게 훓어보기 - 1) 설치
- 그동안의 학습의 여정을 마치고, 더 좋은 코딩 방법을 모아 보고자 벼뤄왔던 g6에 몇가지 기능에 대해서만 간단하게 살펴볼 생각이다. 프로젝트에 포함된 기능이 워낙 많기 때문에, 차후에 조금씩 부분 분석해 볼 예정이다.
- workflow에 대한 분석이 아닌, 코드 스닛펫(code snippet) 위주의 분석을 할 것이다.
- 공식 페이지 : https://sir.kr/main/g6/
- 다운로드 페이지 : https://sir.kr/g6_tags
- github 저장소 : https://github.com/gnuboard/g6
- 메뉴얼 : https://sir.kr/manual/g6/
1. 프로젝트 설치
1) 설치 여부 검사
- inspect(engine).has_table(prefix + "config"))는 SQLAlchemy를 사용하여 데이터베이스에 특정 테이블이 존재하는지 확인하는 코드이다.
1. inspect(engine):
- SQLAlchemy의 inspect 함수를 사용하면 데이터베이스의 메타데이터를 조사할 수 있는 Inspector 객체가 반환된다. - inspect(engine)는 sqlalchemy.engine.reflection.Inspector 인스턴스를 반환하며, 이를 통해 특정 테이블의 컬럼, 인덱스, 제약조건 등의 메타데이터를 조회할 수 있다.
2. has_table(prefix + "config"):
- Inspector 객체의 has_table 메서드는 인자로 전달한 이름의 테이블이 데이터베이스에 존재하는지를 불리언 값(True/False)으로 반환한다. 여기서 prefix + "config"는 접두어(prefix)를 포함한 테이블 이름을 동적으로 생성하는 방식이다.
async def validate_install():
"""설치 여부 검사"""
db_connect = DBConnect()
engine = db_connect.engine
prefix = db_connect.table_prefix
if (os.path.exists(ENV_PATH)
and inspect(engine).has_table(prefix + "config")):
raise AlertException(
"이미 설치가 완료되었습니다.\\n재설치하시려면 .env파일을 삭제 후 다시 시도해주세요.", 400, "/")
2) dependant 와 dependencies의 차이
- dependencies를 이용해 view 스코프가 아닌, router 스코프(view의 집합) 혹은 global app 스코프에 각각 dependencies를 설정할 수 있다.
Dependencies in path operation decorators
In some cases you don't really need the return value of a dependency inside your path operation function. Or the dependency doesn't return a value.
But you still need it to be executed/solved.
For those cases, instead of declaring a path operation function parameter with Depends, you can add a list of dependencies to the path operation decorator.
Origin
# App Scope Dependencies: 모든 요청에 대해 Depends를 실행합니다.
app = FastAPI(dependencies=[Depends(verify_key)])
@app.post(
"/hello-world5/{path}",
# View Scope Dependencies: View로 접수되는 Request에 대해서만 Depends를 실행합니다.
dependencies=[Depends(verify_token)]
)
...
- dependant 와 dependencies의 차이는 다음과 같다.
- dependant는 view 관련 정보를 들고 있는 객체이다.
- dependencies는 dependant의 attr로, view와 연관된 Depends 들을 들고 있는 list라 할 수 있다.
- 주요 차이점
1. 적용 범위:
- dependencies:
- 라우트 수준에서 전체적인 의존성을 지정하며, 모든 해당 라우트 내의 엔드포인트 함수에 적용된다.
- Depends:
- 함수 매개변수 수준에서 개별적으로 각 매개변수에 대한 의존성을 선언하며, 엔드포인트 함수 내의 각 매개변수에 대해 특화된 처리를 할 수 있다.
2. 사용 방법:
- dependencies:
- 데코레이터의 매개변수로 리스트 형태로 여러 의존성을 지정하여 한 번에 관리할 수 있다.
- Depends:
- 함수 매개변수의 기본값으로 사용되며, 필요에 따라 다양한 의존성을 선언할 수 있다.
3. 기능:
- dependencies:
- 모든 의존성을 해결한 후에 해당 라우트 내의 엔드포인트 함수들이 실행된다.
- Depends:
- 각 매개변수의 의존성을 충족해야만 해당 매개변수에 대한 값을 받을 수 있으며, 이를 통해 코드의 모듈화와 재사용성을 높일 수 있다.
3) 토큰 검증
- 세션과 쿼리로 넘어온 토큰을 비교하여 확인을 한다. csrf 구조인데 이름을 ss_token으로 해서 세션 토큰으로 이해가 된다. csrf의 동작에 대해 제공해 주는 기능을 사용만 하다보니 직접 구현을 몰라서 헤메었다.
- 토큰을 세션에 넣고, 폼을 만들때 히든으로 넣어 서버에 요청시 사용하게 만든다. 서버는 세션의 토큰과 폼에 있는 토큰을 비교해 변조가 있는지 확인한다.
- 쿼리에서 토큰 가져오기
async def get_variety_tokens(
token_form: Annotated[str, Form(alias="token")] = None,
token_query: Annotated[str, Query(alias="token")] = None
):
"""
요청 매개변수의 유형별 토큰을 수신, 하나의 토큰만 반환
- 함수의 매개변수 순서대로 우선순위를 가짐
"""
return token_form or token_query
- 토큰 유효성 검사
async def validate_token(
request: Request,
token: Annotated[str, Depends(get_variety_tokens)]
):
"""토큰 유효성 검사"""
if not check_token(request, token):
raise AlertException("토큰이 유효하지 않습니다", 403)
- 세션과 인수로 넘어온 토큰확인 함수
import secrets
from fastapi import Request
def create_session_token(request: Request):
"""
토큰 생성 후 세션에 저장&반환
Args:
request (Request): FastAPI Request 객체
Returns:
str: 생성된 토큰
"""
token = secrets.token_hex(16) # 16바이트 토큰 생성
request.session["ss_token"] = token # 세션에 토큰 저장
return token
def check_token(request: Request, token: str) -> bool:
"""세션과 인수로 넘어온 토큰확인 함수
Args:
request (Request): FastAPI Request 객체
token (str): token 문자열
Returns:
bool: 토큰 일치 여부
"""
if not token:
return False
token = token.strip()
if token == request.session.get("ss_token"):
# 세션 삭제
request.session["ss_token"] = ""
return True
return False
4) Base.metadata.tables.values()에 대하여
1. Base.metadata.tables
- 정의 및 역할:
- Base는 보통 SQLAlchemy의 선언적 베이스(declarative base)로 생성된 객체이다.
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
- 이 Base를 사용해 정의한 모델 클래스들은 내부적으로 자신의 테이블 정보를 Base.metadata에 등록한다.
- Base.metadata는 MetaData 객체로, 데이터베이스 스키마(테이블, 컬럼, 제약조건 등)의 메타정보를 담고 있다.
- Base.metadata.tables는 이 MetaData 객체가 관리하는 모든 테이블 정보를 사전(dict) 형태로 보관한다.
- 반환값:
- 자료구조: 파이썬의 dict
- 키(Key): 테이블 이름(문자열, str)
- 값(Value): 각 테이블에 해당하는 SQLAlchemy의 Table 객체
- 예시:
{
'users': <sqlalchemy.sql.schema.Table object at 0x...>,
'addresses': <sqlalchemy.sql.schema.Table object at 0x...>,
...
}
- 특징:
- 모든 모델 클래스에서 선언한 테이블이 여기에 자동으로 등록된다.
- 만약 아직 테이블이 정의되지 않았다면, 빈 딕셔너리({})가 된다.
2. Base.metadata.tables.values()
- 정의 및 역할:
- Base.metadata.tables.values()는 위에서 설명한 딕셔너리의 값들만을 반환하는 메서드이다.
- 즉, 테이블 이름을 키로 가지는 사전에서, 실제 Table 객체들만 모아서 iterable(반복 가능한 객체)로 반환한다.
- 반환값:
- 자료구조: dict_values 객체 (파이썬의 dict에서 values() 메서드를 호출하면 반환되는 뷰 객체)
- 내용:
- 이 객체에는 데이터베이스에 정의된 모든 테이블에 해당하는 Table 객체들이 포함된다.
- 예시:
tables = Base.metadata.tables.values()
# tables는 다음과 유사한 iterable 객체입니다.
# [<Table 'users'>, <Table 'addresses'>, ...]
- 특징:
- dict_values는 리스트와 비슷하게 동작하며, 반복문 등에서 사용할 수 있다.
- 만약 전체 테이블 객체를 리스트로 사용하고 싶다면, list(Base.metadata.tables.values())처럼 변환할 수 있다.
3. 요약
- Base.metadata.tables:
- SQLAlchemy의 MetaData 객체에 등록된 모든 테이블 정보를 담은 사전이다.
- 키는 테이블 이름, 값은 Table 객체이다.
- Base.metadata.tables.values():
- 위 사전에서 값들(즉, Table 객체들)만을 반환하는 뷰 객체이다.
- 테이블 이름은 포함하지 않고, 테이블 자체만 필요할 때 유용하게 사용된다.
5)동적 게시판 모델 생성
- 원본 코드
_created_models = {}
# 동적 게시판 모델 생성
def dynamic_create_write_table(
table_name: str,
create_table: bool = False,
) -> WriteBaseModel:
'''
WriteBaseModel 로 부터 게시판 테이블 구조를 복사하여 동적 모델로 생성하는 함수
인수의 table_name 에서는 table_prefix + 'write_' 를 제외한 테이블 이름만 입력받는다.
Create Dynamic Write Table Model from WriteBaseModel
'''
# 이미 생성된 모델 반환
if table_name in _created_models:
return _created_models[table_name]
if isinstance(table_name, int):
table_name = str(table_name)
class_name = "Write" + table_name.capitalize()
db_connect = DBConnect()
DynamicModel = type(
class_name,
(WriteBaseModel,),
{
"__tablename__": db_connect.table_prefix + 'write_' + table_name,
"__table_args__": (
Index(f'idx_wr_num_reply_{table_name}', 'wr_num', 'wr_reply'),
Index(f'idex_wr_is_comment_{table_name}', 'wr_is_comment'),
{
"extend_existing": True,
**MySQLCharsetMixin().__table_args__
},
),
}
)
# 게시판 추가시 한번만 테이블 생성
if create_table:
DynamicModel.__table__.create(bind=db_connect.engine, checkfirst=True)
# 생성된 모델 캐싱
_created_models[table_name] = DynamicModel
return DynamicModel
- type은 두가지 용도로 사용 가능하다.
- 객체의 타입 확인
- 동적 클래스 생성
- 이를 이용해 동적으로 게시판 테이블을 정의하고 생성할 수 있다.
- __table__.create()의 매개변수
- bind 매개변수:
- bind를 통해 어느 데이터베이스에 테이블을 생성할 것인지를 지정한다.
- checkfirst 옵션:
- checkfirst=True를 사용하면, 테이블이 이미 존재하는지 확인한 후 존재하지 않을 때만 테이블을 생성한다.
DynamicModel = type(
class_name,
(WriteBaseModel,),
{
"__tablename__": db_connect.table_prefix + 'write_' + table_name,
"__table_args__": (
Index(f'idx_wr_num_reply_{table_name}', 'wr_num', 'wr_reply'),
Index(f'idex_wr_is_comment_{table_name}', 'wr_is_comment'),
{
"extend_existing": True,
**MySQLCharsetMixin().__table_args__
},
),
}
)
# 게시판 추가시 한번만 테이블 생성
if create_table:
DynamicModel.__table__.create(bind=db_connect.engine, checkfirst=True)
# 생성된 모델 캐싱
_created_models[table_name] = DynamicModel
- EventSourceResponse는 주로 서버-발송 이벤트(Server-Sent Events, SSE)를 구현하기 위해 사용되는 응답 객체이다. 주로 Starlette나 FastAPI와 같은 비동기 웹 프레임워크에서 사용되며, 서버에서 클라이언트로 지속적으로 데이터를 스트리밍하는 방식으로 실시간 업데이트를 제공할 수 있게 해준다.
- 아래 코드를 통해 비동기 제너레이터(또는 이터레이터)를 받아 이벤트를 순차적으로 클라이언트에 전송한다.
- yield를 통해 이벤트를 전송한다.
# 설치 진행 이벤트 스트림 실행
return EventSourceResponse(install_event())
- reference :
https://rumbarum.oopy.io/post/how-fastapi-depends-work
FastAPI Depends 톺아보기
How 'Depends()' works in Fastapi Web Frameworks
rumbarum.oopy.io
FastAPI를 배워보자 11일차 - Dependency Injection
https://fastapi.tiangolo.com/tutorial/dependencies/Dependency Injection은 두 가지로 분해해서 의미를 해석할 수 있는데 Dependency는 개발자의 code를 위해 사용하거나, 동작하기를 원하는 것을 선
velog.io
'Study > fastapi' 카테고리의 다른 글
fastapi - G6 간단하게 훓어보기 - 2) API 호출 (0) | 2025.02.21 |
---|---|
depends() 함수를 정의하는 방법들 (0) | 2025.02.19 |
vscode, cusor ai에서 fastapi debug 하는 방법 with launch.json (0) | 2025.02.19 |
FastAPI에서 SQLAlchemy를 사용하여 테이블을 생성하는 방법 (0) | 2025.02.17 |
fastapi의 FastAPI() 메소드 사용법 정리 (0) | 2025.02.16 |
댓글