"""Feature 자동 탐색 + 등록. features/ 하위 디렉토리를 스캔하여 FEATURE_CONFIG가 있는 모듈을 수집하고 order로 정렬. """ import importlib from pathlib import Path _REQUIRED_CONFIG_FIELDS = {"name", "icon", "order"} def discover_features() -> list[dict]: """features/ 하위 디렉토리를 스캔, FEATURE_CONFIG 있는 모듈 수집, order 정렬. Returns: List of dicts with 'config' and 'module' keys, sorted by order. """ features_dir = Path(__file__).parent / "features" if not features_dir.exists(): return [] results = [] for child in sorted(features_dir.iterdir()): if not child.is_dir() or child.name.startswith("_"): continue init_file = child / "__init__.py" if not init_file.exists(): continue try: module = importlib.import_module(f"features.{child.name}") config = getattr(module, "FEATURE_CONFIG", None) if not config or not isinstance(config, dict): continue missing = _REQUIRED_CONFIG_FIELDS - set(config.keys()) if missing: import streamlit as st st.warning(f"Feature '{child.name}': 필수 필드 누락 {missing}") continue if not hasattr(module, "render") or not callable(module.render): import streamlit as st st.warning(f"Feature '{child.name}': render() 함수 없음") continue results.append({ "config": config, "module": module, }) except Exception as e: import streamlit as st st.warning( f"Feature '{child.name}' 로드 실패: " f"{type(e).__name__}: {e}" ) results.sort(key=lambda x: x["config"].get("order", 99)) return results