From 1784d866552574fb04cd070fd7f2d7727ee85026 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Sun, 11 Oct 2026 09:09:47 +0000 Subject: [PATCH] =?UTF-8?q?[CHORE]:=20CI=20ruff=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=20uv.lock(0.17.0)=EA=B3=BC=20=EA=B3=A0=EC=A0=95?= =?UTF-8?q?=ED=95=98=EA=B3=A0=200.17=20=ED=8F=AC=EB=A7=B7=20=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=ED=94=84=ED=8A=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci-quality Format Check 가 최신 ruff 를 설치해 로컬(uv.lock)과 판정이 어긋났음 - ruff 0.17 은 Markdown 안의 Python 코드 블록도 포맷 검사 — 규칙 문서 코드 블록 재포맷 Co-Authored-By: Claude Fable 5.1 --- .claude/rules/04-error-handling.md | 28 ++++++++++++-------- .claude/rules/05-testing.md | 8 +++--- .claude/rules/06-code-style.md | 24 +++++++++-------- .github/copilot-instructions.md | 41 ++++++++++++++++++++---------- .github/workflows/ci-quality.yml | 4 ++- 5 files changed, 65 insertions(+), 40 deletions(-) diff --git a/.claude/rules/04-error-handling.md b/.claude/rules/04-error-handling.md index 6bb7efc..8b6ab79 100644 --- a/.claude/rules/04-error-handling.md +++ b/.claude/rules/04-error-handling.md @@ -60,15 +60,19 @@ except FileNotFoundError: class GraphLMError(Exception): """GraphLM 패키지의 모든 사용자 정의 예외 base.""" + class DataLoadError(GraphLMError): """데이터 로드 실패.""" + class ConfigError(GraphLMError): """잘못된 실험 설정.""" + class ModelError(GraphLMError): """모델 정의/forward 단계 오류.""" + class ReproducibilityError(GraphLMError): """시드 고정 실패 / 결정론 깨짐 감지.""" ``` @@ -84,13 +88,13 @@ base 클래스가 있으면 호출자가 `except GraphLMError` 로 라이브러 ```python from tenacity import retry, stop_after_attempt, wait_exponential + @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), reraise=True, ) -def download_dataset(url: str) -> Path: - ... +def download_dataset(url: str) -> Path: ... ``` 학습 루프 안의 NaN/Inf 감지 같은 "재시도하면 의미 없는" 케이스는 retry 대신 즉시 fail. @@ -121,6 +125,7 @@ import logging logger = logging.getLogger(__name__) + def train_epoch(model, loader, optimizer) -> dict: logger.info("epoch 시작", extra={"n_batches": len(loader)}) for batch_idx, batch in enumerate(loader): @@ -149,12 +154,15 @@ def train_epoch(model, loader, optimizer) -> dict: ```python # Good -logger.info("eval 완료", extra={ - "split": "test", - "accuracy": 0.873, - "loss": 0.42, - "n_samples": 1024, -}) +logger.info( + "eval 완료", + extra={ + "split": "test", + "accuracy": 0.873, + "loss": 0.42, + "n_samples": 1024, + }, +) # Bad — 검색·집계 불가 logger.info(f"eval 완료: test split, acc=0.873, loss=0.42, n=1024") @@ -202,9 +210,7 @@ def assert_deterministic(seed: int, run_fn) -> None: set_seed(seed) result_b = run_fn() if not torch.allclose(result_a, result_b): - raise ReproducibilityError( - f"동일 시드 ({seed}) 에서 결과 불일치 — 결정론 깨짐" - ) + raise ReproducibilityError(f"동일 시드 ({seed}) 에서 결과 불일치 — 결정론 깨짐") ``` CI 또는 디버깅 모드에서 활성화. 매 학습마다는 비용 과다. diff --git a/.claude/rules/05-testing.md b/.claude/rules/05-testing.md index 3372249..01bad15 100644 --- a/.claude/rules/05-testing.md +++ b/.claude/rules/05-testing.md @@ -121,8 +121,8 @@ def small_graph(): ```python @pytest.mark.slow -def test_full_epoch_runs(): - ... +def test_full_epoch_runs(): ... + @pytest.mark.gpu def test_cuda_forward(): @@ -130,9 +130,9 @@ def test_cuda_forward(): pytest.skip("CUDA 미사용") ... + @pytest.mark.network -def test_hf_hub_download(): - ... +def test_hf_hub_download(): ... ``` `pyproject.toml` 에 마커 등록: diff --git a/.claude/rules/06-code-style.md b/.claude/rules/06-code-style.md index 8c030ab..18a45ab 100644 --- a/.claude/rules/06-code-style.md +++ b/.claude/rules/06-code-style.md @@ -60,6 +60,7 @@ def normalize_features( ) -> Tensor: return x / (x.norm(dim=-1, keepdim=True) + eps) + # Bad — 타입 없음 def normalize_features(x, eps=1e-6): return x / (x.norm(dim=-1, keepdim=True) + eps) @@ -70,8 +71,8 @@ def normalize_features(x, eps=1e-6): ```python from typing import TypeAlias -NodeFeatures: TypeAlias = Tensor # shape: [n_nodes, feat_dim] -EdgeIndex: TypeAlias = Tensor # shape: [2, n_edges] +NodeFeatures: TypeAlias = Tensor # shape: [n_nodes, feat_dim] +EdgeIndex: TypeAlias = Tensor # shape: [2, n_edges] GraphBatch: TypeAlias = dict[str, Tensor] ``` @@ -90,6 +91,7 @@ def process(item: Item) -> Result: return item.cache return compute(item) + # Bad — else 중첩 def process(item: Item) -> Result: if item.valid: @@ -143,8 +145,8 @@ class GraphEncoder(nn.Module): super().__init__() ... - def forward(self, x: Tensor, edge_index: Tensor) -> Tensor: - ... + def forward(self, x: Tensor, edge_index: Tensor) -> Tensor: ... + # Bad — 모델이 데이터 로드까지 class GraphEncoder(nn.Module): @@ -287,6 +289,7 @@ def append_item(item, items=[]): items.append(item) return items + # Good def append_item(item, items=None): if items is None: @@ -319,6 +322,7 @@ def is_valid(s: str) -> bool: result = len(s) > 0 return result + # Good def is_valid(s: str) -> bool: return len(s) > 0 @@ -478,16 +482,16 @@ from graphlm.utils import set_seed logger = logging.getLogger(__name__) DEFAULT_HIDDEN_DIM = 128 + # 5. Public API (클래스 / 함수) -class GraphEncoder(nn.Module): - ... +class GraphEncoder(nn.Module): ... + + +def aggregate(x: Tensor, edge_index: Tensor) -> Tensor: ... -def aggregate(x: Tensor, edge_index: Tensor) -> Tensor: - ... # 6. Private helpers -def _normalize(x: Tensor) -> Tensor: - ... +def _normalize(x: Tensor) -> Tensor: ... ``` ## Code Review Checklist diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b173e89..c36075e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -174,10 +174,12 @@ def process(item: Item) -> Result: ```python # ❌ Magic numbers -if score > 0.85: ... +if score > 0.85: + ... # ✅ Named constants ACCEPT_THRESHOLD = 0.85 -if score > ACCEPT_THRESHOLD: ... +if score > ACCEPT_THRESHOLD: + ... ``` ```python @@ -192,10 +194,13 @@ def append(item, items=None): ```python # ❌ bare except -try: risky() -except: pass +try: + risky() +except: + pass # ✅ 구체적 예외 -try: risky() +try: + risky() except (ValueError, RuntimeError) as err: logger.warning("recovered", exc_info=err) ``` @@ -203,6 +208,7 @@ except (ValueError, RuntimeError) as err: ```python # ❌ wildcard import from graphlm.models import * + # ✅ 명시적 import from graphlm.models import GraphEncoder ``` @@ -267,14 +273,18 @@ except FileNotFoundError as err: ```python import logging + logger = logging.getLogger(__name__) # Good — structured -logger.info("eval 완료", extra={ - "split": "test", - "accuracy": 0.873, - "n_samples": 1024, -}) +logger.info( + "eval 완료", + extra={ + "split": "test", + "accuracy": 0.873, + "n_samples": 1024, + }, +) # Bad — 문자열 보간 logger.info(f"eval 완료: acc=0.873, n=1024") @@ -307,10 +317,13 @@ def test_load_dataset_returns_expected_size(tmp_path): ### Parametrize ```python -@pytest.mark.parametrize("input_, expected", [ - ("http://example.com", "https://example.com"), - ("https://www.example.com", "https://example.com"), -]) +@pytest.mark.parametrize( + "input_, expected", + [ + ("http://example.com", "https://example.com"), + ("https://www.example.com", "https://example.com"), + ], +) def test_normalize_url(input_, expected): assert normalize_url(input_) == expected ``` diff --git a/.github/workflows/ci-quality.yml b/.github/workflows/ci-quality.yml index 6da2d12..914c8db 100644 --- a/.github/workflows/ci-quality.yml +++ b/.github/workflows/ci-quality.yml @@ -27,7 +27,9 @@ jobs: cache: pip - name: Install ruff - run: pip install ruff + # uv.lock 의 ruff 버전과 맞춘다 — 버전이 다르면 로컬과 CI 의 포맷 판정이 어긋난다 (이슈 #86). + # ruff 를 올릴 때는 uv.lock · 이 줄 · 포맷 결과를 같은 PR 에서 함께 바꾼다. + run: pip install "ruff==0.17.0" - name: Check ruff format run: |