테스트 강화 및 코드 품질 개선
This commit is contained in:
105
src/state_manager.py
Normal file
105
src/state_manager.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
from .common import DATA_DIR, logger
|
||||
|
||||
# 상태 파일 경로
|
||||
STATE_FILE = str(DATA_DIR / "bot_state.json")
|
||||
|
||||
# 상태 파일 잠금
|
||||
_state_lock = threading.RLock()
|
||||
|
||||
|
||||
def _load_state_unsafe() -> dict[str, Any]:
|
||||
"""내부 사용 전용: Lock 없이 상태 파일 로드"""
|
||||
if os.path.exists(STATE_FILE):
|
||||
try:
|
||||
if os.path.getsize(STATE_FILE) == 0:
|
||||
return {}
|
||||
with open(STATE_FILE, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
logger.warning("[StateManager] 상태 파일 손상됨, 빈 상태 반환: %s", STATE_FILE)
|
||||
return {}
|
||||
except OSError as e:
|
||||
logger.error("[StateManager] 상태 파일 읽기 실패: %s", e)
|
||||
return {}
|
||||
return {}
|
||||
|
||||
|
||||
def _save_state_unsafe(state: dict[str, Any]) -> None:
|
||||
"""내부 사용 전용: Lock 없이 상태 파일 저장 (원자적)"""
|
||||
try:
|
||||
temp_file = f"{STATE_FILE}.tmp"
|
||||
with open(temp_file, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=2, ensure_ascii=False)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
|
||||
os.replace(temp_file, STATE_FILE)
|
||||
except (OSError, TypeError, ValueError) as e:
|
||||
logger.error("[StateManager] 상태 저장 실패: %s", e)
|
||||
|
||||
|
||||
def load_state() -> dict[str, Any]:
|
||||
"""전체 봇 상태를 로드합니다."""
|
||||
with _state_lock:
|
||||
return _load_state_unsafe()
|
||||
|
||||
|
||||
def save_state(state: dict[str, Any]) -> None:
|
||||
"""전체 봇 상태를 저장합니다."""
|
||||
with _state_lock:
|
||||
_save_state_unsafe(state)
|
||||
|
||||
|
||||
def get_value(symbol: str, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
특정 심볼의 상태 값을 조회합니다.
|
||||
예: get_value("KRW-BTC", "max_price", 0.0)
|
||||
"""
|
||||
with _state_lock:
|
||||
state = _load_state_unsafe()
|
||||
symbol_data = state.get(symbol, {})
|
||||
return symbol_data.get(key, default)
|
||||
|
||||
|
||||
def set_value(symbol: str, key: str, value: Any) -> None:
|
||||
"""
|
||||
특정 심볼의 상태 값을 설정하고 저장합니다.
|
||||
예: set_value("KRW-BTC", "max_price", 100000000)
|
||||
"""
|
||||
with _state_lock:
|
||||
state = _load_state_unsafe()
|
||||
if symbol not in state:
|
||||
state[symbol] = {}
|
||||
|
||||
state[symbol][key] = value
|
||||
_save_state_unsafe(state)
|
||||
logger.debug("[StateManager] 상태 업데이트: [%s] %s = %s", symbol, key, value)
|
||||
|
||||
|
||||
def update_max_price_state(symbol: str, current_price: float) -> float:
|
||||
"""
|
||||
최고가(max_price)를 상태 파일에 업데이트합니다.
|
||||
기존 값보다 클 경우에만 업데이트합니다.
|
||||
|
||||
Returns:
|
||||
업데이트된(또는 유지된) max_price
|
||||
"""
|
||||
with _state_lock:
|
||||
state = _load_state_unsafe()
|
||||
if symbol not in state:
|
||||
state[symbol] = {}
|
||||
|
||||
old_max = float(state[symbol].get("max_price", 0.0) or 0.0)
|
||||
|
||||
if current_price > old_max:
|
||||
state[symbol]["max_price"] = current_price
|
||||
_save_state_unsafe(state)
|
||||
logger.debug("[StateManager] [%s] max_price 갱신: %.2f -> %.2f", symbol, old_max, current_price)
|
||||
return current_price
|
||||
|
||||
return old_max
|
||||
Reference in New Issue
Block a user