Coverage for view / web / api_common.py: 100%
51 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
1"""
2웹 API 공통 유틸리티: 컨텍스트 관리, 응답 직렬화, 인증, Pydantic 모델.
3"""
4from fastapi import HTTPException, Request
5from pydantic import BaseModel
7_ctx = None # 전역 변수로 선언
9# API 호출 실패 시 대처를 위한 전역 가격 캐시 (메모리)
10_PRICE_CACHE = {} # {code: (price, rate, timestamp)}
12# 서버 측 in-flight 요청 추적 (hang 진단용): request_id → {path, method, start, query}
13_active_requests: dict = {}
15# 최근 완료 요청 이력 (hang 직전 분석용): deque-like list, 최대 20건
16_recent_completed: list = []
17_RECENT_MAX = 20
20def set_ctx(ctx):
21 global _ctx
22 _ctx = ctx
25def _get_ctx():
26 if _ctx is None:
27 raise HTTPException(status_code=503, detail="서비스가 초기화되지 않았습니다.")
28 return _ctx
31def check_auth(request: Request):
32 """로그인 여부를 확인하는 공통 함수."""
33 ctx = _get_ctx()
34 expected_token = ctx.env.active_config.get("auth", {}).get("secret_key")
35 token = request.cookies.get("access_token")
37 if token != expected_token:
38 raise HTTPException(status_code=401, detail="Unauthorized")
39 return True
42def _serialize_response(resp):
43 """ResCommonResponse를 JSON 직렬화 가능한 dict로 변환."""
44 if resp is None:
45 return {"rt_cd": "999", "msg1": "응답 없음", "data": None}
46 if hasattr(resp, 'to_dict'):
47 return resp.to_dict()
48 return {"rt_cd": str(resp.rt_cd), "msg1": str(resp.msg1), "data": resp.data}
51def _serialize_list_items(items):
52 """dataclass 리스트를 dict 리스트로 변환."""
53 result = []
54 for item in (items or []):
55 if hasattr(item, 'to_dict'):
56 result.append(item.to_dict())
57 elif isinstance(item, dict):
58 result.append(item)
59 else:
60 result.append(str(item))
61 return result
64# --- Pydantic 모델 ---
66class OrderRequest(BaseModel):
67 code: str
68 price: str
69 qty: str
70 side: str # "buy" or "sell"
73class EnvironmentRequest(BaseModel):
74 is_paper: bool
77class ProgramTradingRequest(BaseModel):
78 code: str
81class ProgramTradingUnsubscribeRequest(BaseModel):
82 code: str | None = None
85class ProgramTradingDataModel(BaseModel):
86 chartData: dict
87 subscribedCodes: list
88 codeNameMap: dict
89 savedAt: str | None = None