feat(M01): 마스터 데이터 관리자 API 서버 쪽 — 읽기 · 시험 계산 · 저장
- M01_MasterData 라우터 + 파일 저장소 (/api/m01 · 시스템관리자 전용) - 저장 = 판본 대조(409) → 적용 → 틀·로직 변수 검사(새 걸림 422) → 씀 - check_master.py 에 load(folder) · check_saved · calc 드러냄 - 계약 문서 장 이름 보정(공통3장) - 시험 test_m01_api.py 8개 (임시 폴더 사본) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""M01 마스터 데이터 관리자 API — 계약 `resources/master_data/_화면_계약.md`.
|
||||
|
||||
⚠ 권한은 등록하는 쪽(`main.py`)이 `system_admin_only` 로 붙임.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as store
|
||||
|
||||
router = APIRouter(prefix="/api/m01", tags=["M01 MasterData"])
|
||||
|
||||
|
||||
class CalcBody(BaseModel):
|
||||
book: str
|
||||
key: str
|
||||
inputs: dict[str, Any] = {}
|
||||
|
||||
|
||||
class Change(BaseModel):
|
||||
op: Literal["edit", "add", "delete"]
|
||||
key: str | None = None
|
||||
row: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class FileChanges(BaseModel):
|
||||
file: str
|
||||
version: str
|
||||
changes: list[Change]
|
||||
|
||||
|
||||
class SaveBody(BaseModel):
|
||||
files: list[FileChanges]
|
||||
|
||||
|
||||
def _num(v):
|
||||
"""Decimal → JSON 수(그냥 두면 글자로 나감)."""
|
||||
if isinstance(v, Decimal):
|
||||
return int(v) if v == v.to_integral_value() else float(v)
|
||||
if isinstance(v, list):
|
||||
return [_num(x) for x in v]
|
||||
if isinstance(v, dict):
|
||||
return {k: _num(x) for k, x in v.items()}
|
||||
return v
|
||||
|
||||
|
||||
def _call(fn, *args):
|
||||
try:
|
||||
return _num(fn(*args))
|
||||
except store.StoreError as e:
|
||||
raise HTTPException(status_code=e.status, detail=e.detail) from e
|
||||
|
||||
|
||||
@router.get("/groups")
|
||||
def get_groups() -> dict:
|
||||
return {"groups": _call(store.groups)}
|
||||
|
||||
|
||||
@router.get("/groups/{group}/files")
|
||||
def get_files(group: str) -> dict:
|
||||
return {"files": _call(store.files_of, group)}
|
||||
|
||||
|
||||
@router.get("/rows")
|
||||
def get_rows(file: str, page: int = 1, size: int = 50, q: str = "") -> dict:
|
||||
return _call(store.rows, file, page, size, q)
|
||||
|
||||
|
||||
@router.get("/tables")
|
||||
def get_tables(file: str, q: str = "") -> dict:
|
||||
return _call(store.tables, file, q)
|
||||
|
||||
|
||||
@router.get("/table")
|
||||
def get_table(file: str, key: str) -> dict:
|
||||
return _call(store.table, file, key)
|
||||
|
||||
|
||||
@router.get("/logics")
|
||||
def get_logics(book: str = "", chapter: str = "", q: str = "", blocked: int | None = None) -> dict:
|
||||
return {"logics": _call(store.logics, book, chapter, q, blocked)}
|
||||
|
||||
|
||||
@router.get("/logic")
|
||||
def get_logic(book: str, key: str) -> dict:
|
||||
return _call(store.logic, book, key)
|
||||
|
||||
|
||||
@router.post("/calc")
|
||||
def post_calc(body: CalcBody) -> dict:
|
||||
return _call(store.calc, body.book, body.key, body.inputs)
|
||||
|
||||
|
||||
@router.post("/save")
|
||||
def post_save(body: SaveBody) -> dict:
|
||||
return {"files": _call(store.save, [f.model_dump() for f in body.files])}
|
||||
Reference in New Issue
Block a user