refactor(#28): IMP-28 L4 _parse_json dedup (4 modules -> src/json_utils)
Consolidate duplicate _parse_json helpers from content_editor.py /
design_director.py / kei_client.py (fuller form) and pipeline.py (simple form)
into shared src/json_utils.parse_json (strict superset). All 18 call-sites
preserved via `parse_json as _parse_json` alias import; no behavior change.
- src/json_utils.py (new): shared helper, fenced/plain-fence/bare-brace patterns
+ list-prefix cleanup fallback.
- tests/test_json_utils.py (new): 9 unit tests pinning parser semantics.
- src/content_editor.py / design_director.py: remove local helper +
unused `import json` / `import re`.
- src/kei_client.py / pipeline.py: remove local helper; `json` / `re` retained
(used elsewhere).
Targeted tests 9 passed; full pytest 374 passed (3 pre-existing scripts/
collection errors reproduce on baseline 909bf75, IMP-28 unrelated).
This commit is contained in:
@@ -5,9 +5,7 @@ Step B: 프리셋 안에서 블록 매핑 + 글자 수 가이드 (Sonnet)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -15,6 +13,7 @@ import httpx
|
||||
import yaml
|
||||
|
||||
from src.config import settings
|
||||
from src.json_utils import parse_json as _parse_json
|
||||
from src.sse_utils import stream_sse_tokens
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1066,38 +1065,3 @@ def _validate_height_budget(blocks: list[dict], preset: dict) -> list[dict]:
|
||||
})
|
||||
|
||||
return overflows
|
||||
|
||||
|
||||
def _parse_json(text: str) -> dict[str, Any] | None:
|
||||
"""텍스트에서 JSON을 추출한다.
|
||||
|
||||
Kei API가 마크다운 리스트 접두사(- )를 붙여 응답하는 경우에도 처리.
|
||||
"""
|
||||
# 전처리: 각 줄 앞의 마크다운 리스트 접두사(- ) 제거
|
||||
lines = text.split("\n")
|
||||
cleaned_lines = []
|
||||
for line in lines:
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("- "):
|
||||
cleaned_lines.append(stripped[2:])
|
||||
elif stripped.startswith("* "):
|
||||
cleaned_lines.append(stripped[2:])
|
||||
else:
|
||||
cleaned_lines.append(stripped)
|
||||
cleaned = "\n".join(cleaned_lines)
|
||||
|
||||
# 원본 먼저 시도 → 클린 버전 시도
|
||||
for target in [text, cleaned]:
|
||||
patterns = [
|
||||
r"```json\s*(.*?)```",
|
||||
r"```\s*(.*?)```",
|
||||
r"(\{.*\})",
|
||||
]
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, target, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group(1).strip())
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user