Metadata-Version: 2.4
Name: rscc-common
Version: 0.2.0
Summary: RSCC 공통 Python 라이브러리 — 로깅, X-Trace-Id 전파, CommonResponse, ResultCode, 텍스트 유틸
Project-URL: Homepage, https://github.com/Jeonghyeon-Ryu/r-common
Project-URL: Repository, https://github.com/Jeonghyeon-Ryu/r-common
Project-URL: Changelog, https://github.com/Jeonghyeon-Ryu/r-common/releases
Author: Jeonghyeon-Ryu
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
Requires-Dist: httpx>=0.27; extra == 'fastapi'
Description-Content-Type: text/markdown

# rscc-common

RSCC 공통 Python 라이브러리 — 공통 로깅, X-Trace-Id 분산 트레이스 전파, CommonResponse
응답 봉투, ResultCode 에러 코드, 한글 초성 유틸. 기본 설치는 **의존성 0** (표준 라이브러리만) —
FastAPI/pydantic 통합은 `[fastapi]` extra 로 opt-in 한다.

Java(`com.rscc:rscc-common-*`)·JS(`@rscc/common-*`) 와 같은 저장소에서 **와이어 계약을
공유하는 폴리글랏 라이브러리**다 — 응답 봉투/에러 코드/트레이스 헤더 규약의 단일 소스는
[contracts/](https://github.com/Jeonghyeon-Ryu/r-common/tree/master/contracts).

- 저장소: <https://github.com/Jeonghyeon-Ryu/r-common>
- 변경 이력: [CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md)
- 라이선스: MIT

## 설치

```bash
pip install rscc-common               # 기본 — 의존성 0 (errors / logging / trace / text)
pip install "rscc-common[fastapi]"    # + fastapi>=0.110, httpx>=0.27 (response 모듈 사용 가능)
```

PyPI 대신 git 태그로 직설치할 수도 있다 (pip 은 서브디렉토리 설치를 지원):

```bash
pip install "rscc-common[fastapi] @ git+https://github.com/Jeonghyeon-Ryu/r-common.git@py-v0.2.0#subdirectory=python/rscc-common"
# extra 불필요 시: "rscc-common @ git+https://...#subdirectory=python/rscc-common"
```

(py-v0.2.0 릴리스 후 유효 — 최신 태그는
[CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md) 참조.
`py-v0.1.0` 은 이 문서의 API 일부(`reset_trace_id` 등)가 없는 릴리스 인프라 도입 이전 태그 —
사용하지 말 것.)

개발(저장소 체크아웃 안, editable — `--reload` 에 즉시 반영):
`pip install -e "python/rscc-common[fastapi]"`

Python **>= 3.11** (상한 없음). 타입 힌트 포함 (`py.typed` — 소비 측 mypy/pyright 가 읽음).

## 모듈

| 모듈 | 의존성 | 공개 API |
|---|---|---|
| `rscc_common` (최상위) | 없음 | `__version__` + 무의존 심볼 재노출 (`ResultCode`, `configure_logging`, `get_trace_id`, `set_trace_id`, `reset_trace_id`, `new_trace_id`, `install_trace_middleware`, `trace_request_hook`, `async_trace_request_hook`) |
| `rscc_common.errors` | 없음 | `ResultCode` enum — 멤버별 `.code`(str, 와이어는 JSON 문자열) / `.http_status`(int) / `.message`(기본 한국어 메시지). [contracts/error-codes.yaml](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/contracts/error-codes.yaml) 과 정확히 동기 (테스트로 강제) |
| `rscc_common.logging` | 없음 | `configure_logging(log_dir, level, app_name, *, noisy_loggers=..., quiet_uvicorn=...)` — 콘솔 + 자정 롤링 파일(전날분 gzip, 30일 보관). 명시 호출형 (임포트 부작용 없음), 재호출 시 기존 핸들러 close 후 교체 |
| `rscc_common.trace` | 없음 (순수 ASGI/덕타이핑) | [contracts/trace.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/contracts/trace.md) 구현 — `install_trace_middleware(app)`, `get_trace_id()`, `set_trace_id(id) -> Token`, `reset_trace_id(token)`, `new_trace_id()`, httpx 발신 훅 `trace_request_hook` / `async_trace_request_hook`, 상수 `TRACE_HEADER` |
| `rscc_common.response` | **[fastapi] extra 전용** (pydantic) | `CommonResponse` — 와이어 키 `success/code/message/data`, 팩토리 `ok(data)` / `fail(result_code, message=None)`, `to_wire()` 가 `data=None` 키 생략을 강제 ([contracts/common-response.schema.json](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/contracts/common-response.schema.json)) |
| `rscc_common.text.chosung` | 없음 | `of(s)` — 한글 초성 변환 (예: `"검색 엔진"` → `"ㄱㅅㅇㅈ"`), `is_chosung_query(s)` — 초성 질의 여부. search-api `Chosung.java` / ai-core `chosung.py` 와 골든 벡터 파리티 |

## 사용 예

```python
from fastapi import FastAPI
import httpx

from rscc_common import configure_logging, install_trace_middleware, trace_request_hook
from rscc_common.errors import ResultCode
from rscc_common.response import CommonResponse   # [fastapi] extra 필요

configure_logging(log_dir="logs", app_name="ai-core",
                  noisy_loggers=("httpx", "httpcore", "urllib3"), quiet_uvicorn=True)

app = FastAPI()
install_trace_middleware(app)   # X-Trace-Id 수신 재사용 → contextvar → 응답 에코

# 다운스트림 전파 (trace.md 4단계)
client = httpx.Client(event_hooks={"request": [trace_request_hook]})

@app.get("/thing")
def get_thing():
    return CommonResponse.ok({"id": 1}).to_wire()          # data 포함

@app.get("/missing")
def missing():
    return CommonResponse.fail(ResultCode.NOT_FOUND).to_wire()  # data 키 부재
```

요청 컨텍스트 밖(백그라운드 태스크 등)에서 traceId 를 수동 바인딩할 때:

```python
from rscc_common import new_trace_id, reset_trace_id, set_trace_id

token = set_trace_id(new_trace_id())   # 또는 요청에서 캡처해 둔 traceId
try:
    do_background_work()               # 이 안의 로그·httpx 발신에 traceId 가 실린다
finally:
    reset_trace_id(token)              # 이전 값 복원 (권장) — 일회성 태스크는 set_trace_id(None) 도 가능
```

## 알려진 제약

- **트레이스 미들웨어는 미처리 예외의 500 응답에 `X-Trace-Id` 를 에코하지 못한다** —
  `app.add_middleware` 등록이 Starlette `ServerErrorMiddleware` 안쪽에서 실행되기 때문.
  `HTTPException`·검증 오류 등 예외 핸들러가 처리하는 경로(4xx 등)는 정상 에코된다.
  상세·우회책은 `rscc_common.trace.install_trace_middleware` docstring 참조.
- `rscc_common.response` 는 **`[fastapi]` extra 설치 후에만 임포트 가능** —
  기본 설치에서 임포트하면 `ModuleNotFoundError` (pydantic 부재). 나머지 모듈은 기본 설치로 동작.
- FastAPI 핸들러에서 `CommonResponse` 모델을 **직접 반환하지 말고 반드시 `.to_wire()`** 를
  반환할 것 — 직접 반환하면 `data: null` 이 와이어에 실려 "실패/무데이터 시 data 키 생략" 계약이 깨진다.
- `configure_logging` 은 **단일 프로세스**(단일 uvicorn worker) 전제 — 멀티 worker 가 같은
  로그 파일을 회전시키면 안전하지 않다. `level` 기본값은 `logging.DEBUG` — 프로덕션은
  `logging.INFO` 권장.
- `text.chosung.of()` 의 공백 스킵은 스페이스/탭만 (개행·전각 공백은 통과) —
  Java `Chosung.java` 와의 파리티 제약.

## 테스트 (저장소 체크아웃에서)

```bash
cd python/rscc-common
python -m pytest -q    # pyproject 의 pythonpath=["src"] 덕에 설치 없이 바로 동작
```

테스트 의존성은 pyproject 의 `[dependency-groups] dev` (pytest, pyyaml, fastapi>=0.110,
httpx>=0.27) — pip 25.1+ 는 `pip install --group dev`. `tests/test_errors.py` 는 저장소의
`contracts/error-codes.yaml` 과 enum 정합을 비교하며, 파일이 없는 환경(sdist 등)에서는 자동 skip 된다.

## 버전·릴리스

- **버전 단일 소스**: `src/rscc_common/__init__.py` 의 `__version__` —
  `pyproject.toml` 은 dynamic version 으로 이 값을 읽는다 (릴리스 시 pyproject 수정 불필요).
- `py-vX.Y.Z` 태그가 push 되면 GitHub Actions
  ([release-python.yml](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/.github/workflows/release-python.yml))
  이 태그↔`__version__` 정합 검증 → `python -m build` → **PyPI Trusted Publishing (OIDC)**
  업로드 → GitHub Release 생성.
- 절차 상세(runbook)·릴리스 이력:
  [루트 README](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/README.md) ·
  [CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md)
