rscc-common 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rscc_common-0.2.0/.gitignore +40 -0
- rscc_common-0.2.0/LICENSE +21 -0
- rscc_common-0.2.0/PKG-INFO +151 -0
- rscc_common-0.2.0/README.md +127 -0
- rscc_common-0.2.0/pyproject.toml +59 -0
- rscc_common-0.2.0/src/rscc_common/__init__.py +97 -0
- rscc_common-0.2.0/src/rscc_common/access_log.py +200 -0
- rscc_common-0.2.0/src/rscc_common/api_key.py +90 -0
- rscc_common-0.2.0/src/rscc_common/bizno.py +75 -0
- rscc_common-0.2.0/src/rscc_common/bulkhead.py +133 -0
- rscc_common-0.2.0/src/rscc_common/cache.py +297 -0
- rscc_common-0.2.0/src/rscc_common/circuit_breaker.py +188 -0
- rscc_common-0.2.0/src/rscc_common/client_ip.py +221 -0
- rscc_common-0.2.0/src/rscc_common/constant_time.py +27 -0
- rscc_common-0.2.0/src/rscc_common/datetimes.py +90 -0
- rscc_common-0.2.0/src/rscc_common/errors.py +48 -0
- rscc_common-0.2.0/src/rscc_common/handlers.py +160 -0
- rscc_common-0.2.0/src/rscc_common/jwt.py +227 -0
- rscc_common-0.2.0/src/rscc_common/log_sanitize.py +47 -0
- rscc_common-0.2.0/src/rscc_common/logging.py +118 -0
- rscc_common-0.2.0/src/rscc_common/masking.py +101 -0
- rscc_common-0.2.0/src/rscc_common/metrics.py +43 -0
- rscc_common-0.2.0/src/rscc_common/py.typed +0 -0
- rscc_common-0.2.0/src/rscc_common/rate_limit.py +227 -0
- rscc_common-0.2.0/src/rscc_common/response.py +73 -0
- rscc_common-0.2.0/src/rscc_common/retry.py +163 -0
- rscc_common-0.2.0/src/rscc_common/security_headers.py +102 -0
- rscc_common-0.2.0/src/rscc_common/sse.py +77 -0
- rscc_common-0.2.0/src/rscc_common/text/__init__.py +1 -0
- rscc_common-0.2.0/src/rscc_common/text/chosung.py +59 -0
- rscc_common-0.2.0/src/rscc_common/trace.py +160 -0
- rscc_common-0.2.0/src/rscc_common/user_context.py +188 -0
- rscc_common-0.2.0/src/rscc_common/validation.py +45 -0
- rscc_common-0.2.0/tests/test_access_log.py +333 -0
- rscc_common-0.2.0/tests/test_api_key.py +108 -0
- rscc_common-0.2.0/tests/test_bizno.py +83 -0
- rscc_common-0.2.0/tests/test_bulkhead.py +278 -0
- rscc_common-0.2.0/tests/test_cache.py +450 -0
- rscc_common-0.2.0/tests/test_chosung.py +49 -0
- rscc_common-0.2.0/tests/test_circuit_breaker.py +188 -0
- rscc_common-0.2.0/tests/test_client_ip.py +229 -0
- rscc_common-0.2.0/tests/test_constant_time.py +45 -0
- rscc_common-0.2.0/tests/test_datetimes.py +52 -0
- rscc_common-0.2.0/tests/test_errors.py +79 -0
- rscc_common-0.2.0/tests/test_handlers.py +203 -0
- rscc_common-0.2.0/tests/test_jwt.py +172 -0
- rscc_common-0.2.0/tests/test_log_sanitize.py +89 -0
- rscc_common-0.2.0/tests/test_logging.py +196 -0
- rscc_common-0.2.0/tests/test_masking.py +57 -0
- rscc_common-0.2.0/tests/test_metrics.py +68 -0
- rscc_common-0.2.0/tests/test_rate_limit.py +271 -0
- rscc_common-0.2.0/tests/test_response.py +110 -0
- rscc_common-0.2.0/tests/test_retry.py +316 -0
- rscc_common-0.2.0/tests/test_security_headers.py +94 -0
- rscc_common-0.2.0/tests/test_sse.py +59 -0
- rscc_common-0.2.0/tests/test_trace.py +186 -0
- rscc_common-0.2.0/tests/test_user_context.py +242 -0
- rscc_common-0.2.0/tests/test_validation.py +64 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Java
|
|
2
|
+
target/
|
|
3
|
+
*.class
|
|
4
|
+
.flattened-pom.xml
|
|
5
|
+
|
|
6
|
+
# JS
|
|
7
|
+
node_modules/
|
|
8
|
+
dist/
|
|
9
|
+
*.tgz
|
|
10
|
+
*.tsbuildinfo
|
|
11
|
+
coverage/
|
|
12
|
+
|
|
13
|
+
# Python
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.egg-info/
|
|
18
|
+
build/
|
|
19
|
+
*.whl
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.coverage
|
|
24
|
+
htmlcov/
|
|
25
|
+
# 참고: python -m build 의 산출물(dist/)은 위 JS 절의 dist/ 패턴이
|
|
26
|
+
# 경로 전체에 적용되므로 별도 항목 없이 함께 무시된다.
|
|
27
|
+
|
|
28
|
+
# 로그 (rscc_common.logging 이 자정 롤링 gzip 로그 파일을 생성)
|
|
29
|
+
logs/
|
|
30
|
+
*.log
|
|
31
|
+
|
|
32
|
+
# IDE / OS
|
|
33
|
+
.idea/
|
|
34
|
+
*.iml
|
|
35
|
+
.vscode/
|
|
36
|
+
.DS_Store
|
|
37
|
+
|
|
38
|
+
# 환경 파일은 커밋 금지
|
|
39
|
+
.env
|
|
40
|
+
.env.*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jeonghyeon-Ryu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rscc-common
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: RSCC 공통 Python 라이브러리 — 로깅, X-Trace-Id 전파, CommonResponse, ResultCode, 텍스트 유틸
|
|
5
|
+
Project-URL: Homepage, https://github.com/Jeonghyeon-Ryu/r-common
|
|
6
|
+
Project-URL: Repository, https://github.com/Jeonghyeon-Ryu/r-common
|
|
7
|
+
Project-URL: Changelog, https://github.com/Jeonghyeon-Ryu/r-common/releases
|
|
8
|
+
Author: Jeonghyeon-Ryu
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Provides-Extra: fastapi
|
|
21
|
+
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
22
|
+
Requires-Dist: httpx>=0.27; extra == 'fastapi'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# rscc-common
|
|
26
|
+
|
|
27
|
+
RSCC 공통 Python 라이브러리 — 공통 로깅, X-Trace-Id 분산 트레이스 전파, CommonResponse
|
|
28
|
+
응답 봉투, ResultCode 에러 코드, 한글 초성 유틸. 기본 설치는 **의존성 0** (표준 라이브러리만) —
|
|
29
|
+
FastAPI/pydantic 통합은 `[fastapi]` extra 로 opt-in 한다.
|
|
30
|
+
|
|
31
|
+
Java(`com.rscc:rscc-common-*`)·JS(`@rscc/common-*`) 와 같은 저장소에서 **와이어 계약을
|
|
32
|
+
공유하는 폴리글랏 라이브러리**다 — 응답 봉투/에러 코드/트레이스 헤더 규약의 단일 소스는
|
|
33
|
+
[contracts/](https://github.com/Jeonghyeon-Ryu/r-common/tree/master/contracts).
|
|
34
|
+
|
|
35
|
+
- 저장소: <https://github.com/Jeonghyeon-Ryu/r-common>
|
|
36
|
+
- 변경 이력: [CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md)
|
|
37
|
+
- 라이선스: MIT
|
|
38
|
+
|
|
39
|
+
## 설치
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install rscc-common # 기본 — 의존성 0 (errors / logging / trace / text)
|
|
43
|
+
pip install "rscc-common[fastapi]" # + fastapi>=0.110, httpx>=0.27 (response 모듈 사용 가능)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
PyPI 대신 git 태그로 직설치할 수도 있다 (pip 은 서브디렉토리 설치를 지원):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install "rscc-common[fastapi] @ git+https://github.com/Jeonghyeon-Ryu/r-common.git@py-v0.2.0#subdirectory=python/rscc-common"
|
|
50
|
+
# extra 불필요 시: "rscc-common @ git+https://...#subdirectory=python/rscc-common"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
(py-v0.2.0 릴리스 후 유효 — 최신 태그는
|
|
54
|
+
[CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md) 참조.
|
|
55
|
+
`py-v0.1.0` 은 이 문서의 API 일부(`reset_trace_id` 등)가 없는 릴리스 인프라 도입 이전 태그 —
|
|
56
|
+
사용하지 말 것.)
|
|
57
|
+
|
|
58
|
+
개발(저장소 체크아웃 안, editable — `--reload` 에 즉시 반영):
|
|
59
|
+
`pip install -e "python/rscc-common[fastapi]"`
|
|
60
|
+
|
|
61
|
+
Python **>= 3.11** (상한 없음). 타입 힌트 포함 (`py.typed` — 소비 측 mypy/pyright 가 읽음).
|
|
62
|
+
|
|
63
|
+
## 모듈
|
|
64
|
+
|
|
65
|
+
| 모듈 | 의존성 | 공개 API |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `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`) |
|
|
68
|
+
| `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) 과 정확히 동기 (테스트로 강제) |
|
|
69
|
+
| `rscc_common.logging` | 없음 | `configure_logging(log_dir, level, app_name, *, noisy_loggers=..., quiet_uvicorn=...)` — 콘솔 + 자정 롤링 파일(전날분 gzip, 30일 보관). 명시 호출형 (임포트 부작용 없음), 재호출 시 기존 핸들러 close 후 교체 |
|
|
70
|
+
| `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` |
|
|
71
|
+
| `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)) |
|
|
72
|
+
| `rscc_common.text.chosung` | 없음 | `of(s)` — 한글 초성 변환 (예: `"검색 엔진"` → `"ㄱㅅㅇㅈ"`), `is_chosung_query(s)` — 초성 질의 여부. search-api `Chosung.java` / ai-core `chosung.py` 와 골든 벡터 파리티 |
|
|
73
|
+
|
|
74
|
+
## 사용 예
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from fastapi import FastAPI
|
|
78
|
+
import httpx
|
|
79
|
+
|
|
80
|
+
from rscc_common import configure_logging, install_trace_middleware, trace_request_hook
|
|
81
|
+
from rscc_common.errors import ResultCode
|
|
82
|
+
from rscc_common.response import CommonResponse # [fastapi] extra 필요
|
|
83
|
+
|
|
84
|
+
configure_logging(log_dir="logs", app_name="ai-core",
|
|
85
|
+
noisy_loggers=("httpx", "httpcore", "urllib3"), quiet_uvicorn=True)
|
|
86
|
+
|
|
87
|
+
app = FastAPI()
|
|
88
|
+
install_trace_middleware(app) # X-Trace-Id 수신 재사용 → contextvar → 응답 에코
|
|
89
|
+
|
|
90
|
+
# 다운스트림 전파 (trace.md 4단계)
|
|
91
|
+
client = httpx.Client(event_hooks={"request": [trace_request_hook]})
|
|
92
|
+
|
|
93
|
+
@app.get("/thing")
|
|
94
|
+
def get_thing():
|
|
95
|
+
return CommonResponse.ok({"id": 1}).to_wire() # data 포함
|
|
96
|
+
|
|
97
|
+
@app.get("/missing")
|
|
98
|
+
def missing():
|
|
99
|
+
return CommonResponse.fail(ResultCode.NOT_FOUND).to_wire() # data 키 부재
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
요청 컨텍스트 밖(백그라운드 태스크 등)에서 traceId 를 수동 바인딩할 때:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from rscc_common import new_trace_id, reset_trace_id, set_trace_id
|
|
106
|
+
|
|
107
|
+
token = set_trace_id(new_trace_id()) # 또는 요청에서 캡처해 둔 traceId
|
|
108
|
+
try:
|
|
109
|
+
do_background_work() # 이 안의 로그·httpx 발신에 traceId 가 실린다
|
|
110
|
+
finally:
|
|
111
|
+
reset_trace_id(token) # 이전 값 복원 (권장) — 일회성 태스크는 set_trace_id(None) 도 가능
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 알려진 제약
|
|
115
|
+
|
|
116
|
+
- **트레이스 미들웨어는 미처리 예외의 500 응답에 `X-Trace-Id` 를 에코하지 못한다** —
|
|
117
|
+
`app.add_middleware` 등록이 Starlette `ServerErrorMiddleware` 안쪽에서 실행되기 때문.
|
|
118
|
+
`HTTPException`·검증 오류 등 예외 핸들러가 처리하는 경로(4xx 등)는 정상 에코된다.
|
|
119
|
+
상세·우회책은 `rscc_common.trace.install_trace_middleware` docstring 참조.
|
|
120
|
+
- `rscc_common.response` 는 **`[fastapi]` extra 설치 후에만 임포트 가능** —
|
|
121
|
+
기본 설치에서 임포트하면 `ModuleNotFoundError` (pydantic 부재). 나머지 모듈은 기본 설치로 동작.
|
|
122
|
+
- FastAPI 핸들러에서 `CommonResponse` 모델을 **직접 반환하지 말고 반드시 `.to_wire()`** 를
|
|
123
|
+
반환할 것 — 직접 반환하면 `data: null` 이 와이어에 실려 "실패/무데이터 시 data 키 생략" 계약이 깨진다.
|
|
124
|
+
- `configure_logging` 은 **단일 프로세스**(단일 uvicorn worker) 전제 — 멀티 worker 가 같은
|
|
125
|
+
로그 파일을 회전시키면 안전하지 않다. `level` 기본값은 `logging.DEBUG` — 프로덕션은
|
|
126
|
+
`logging.INFO` 권장.
|
|
127
|
+
- `text.chosung.of()` 의 공백 스킵은 스페이스/탭만 (개행·전각 공백은 통과) —
|
|
128
|
+
Java `Chosung.java` 와의 파리티 제약.
|
|
129
|
+
|
|
130
|
+
## 테스트 (저장소 체크아웃에서)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd python/rscc-common
|
|
134
|
+
python -m pytest -q # pyproject 의 pythonpath=["src"] 덕에 설치 없이 바로 동작
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
테스트 의존성은 pyproject 의 `[dependency-groups] dev` (pytest, pyyaml, fastapi>=0.110,
|
|
138
|
+
httpx>=0.27) — pip 25.1+ 는 `pip install --group dev`. `tests/test_errors.py` 는 저장소의
|
|
139
|
+
`contracts/error-codes.yaml` 과 enum 정합을 비교하며, 파일이 없는 환경(sdist 등)에서는 자동 skip 된다.
|
|
140
|
+
|
|
141
|
+
## 버전·릴리스
|
|
142
|
+
|
|
143
|
+
- **버전 단일 소스**: `src/rscc_common/__init__.py` 의 `__version__` —
|
|
144
|
+
`pyproject.toml` 은 dynamic version 으로 이 값을 읽는다 (릴리스 시 pyproject 수정 불필요).
|
|
145
|
+
- `py-vX.Y.Z` 태그가 push 되면 GitHub Actions
|
|
146
|
+
([release-python.yml](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/.github/workflows/release-python.yml))
|
|
147
|
+
이 태그↔`__version__` 정합 검증 → `python -m build` → **PyPI Trusted Publishing (OIDC)**
|
|
148
|
+
업로드 → GitHub Release 생성.
|
|
149
|
+
- 절차 상세(runbook)·릴리스 이력:
|
|
150
|
+
[루트 README](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/README.md) ·
|
|
151
|
+
[CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# rscc-common
|
|
2
|
+
|
|
3
|
+
RSCC 공통 Python 라이브러리 — 공통 로깅, X-Trace-Id 분산 트레이스 전파, CommonResponse
|
|
4
|
+
응답 봉투, ResultCode 에러 코드, 한글 초성 유틸. 기본 설치는 **의존성 0** (표준 라이브러리만) —
|
|
5
|
+
FastAPI/pydantic 통합은 `[fastapi]` extra 로 opt-in 한다.
|
|
6
|
+
|
|
7
|
+
Java(`com.rscc:rscc-common-*`)·JS(`@rscc/common-*`) 와 같은 저장소에서 **와이어 계약을
|
|
8
|
+
공유하는 폴리글랏 라이브러리**다 — 응답 봉투/에러 코드/트레이스 헤더 규약의 단일 소스는
|
|
9
|
+
[contracts/](https://github.com/Jeonghyeon-Ryu/r-common/tree/master/contracts).
|
|
10
|
+
|
|
11
|
+
- 저장소: <https://github.com/Jeonghyeon-Ryu/r-common>
|
|
12
|
+
- 변경 이력: [CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md)
|
|
13
|
+
- 라이선스: MIT
|
|
14
|
+
|
|
15
|
+
## 설치
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install rscc-common # 기본 — 의존성 0 (errors / logging / trace / text)
|
|
19
|
+
pip install "rscc-common[fastapi]" # + fastapi>=0.110, httpx>=0.27 (response 모듈 사용 가능)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
PyPI 대신 git 태그로 직설치할 수도 있다 (pip 은 서브디렉토리 설치를 지원):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install "rscc-common[fastapi] @ git+https://github.com/Jeonghyeon-Ryu/r-common.git@py-v0.2.0#subdirectory=python/rscc-common"
|
|
26
|
+
# extra 불필요 시: "rscc-common @ git+https://...#subdirectory=python/rscc-common"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
(py-v0.2.0 릴리스 후 유효 — 최신 태그는
|
|
30
|
+
[CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md) 참조.
|
|
31
|
+
`py-v0.1.0` 은 이 문서의 API 일부(`reset_trace_id` 등)가 없는 릴리스 인프라 도입 이전 태그 —
|
|
32
|
+
사용하지 말 것.)
|
|
33
|
+
|
|
34
|
+
개발(저장소 체크아웃 안, editable — `--reload` 에 즉시 반영):
|
|
35
|
+
`pip install -e "python/rscc-common[fastapi]"`
|
|
36
|
+
|
|
37
|
+
Python **>= 3.11** (상한 없음). 타입 힌트 포함 (`py.typed` — 소비 측 mypy/pyright 가 읽음).
|
|
38
|
+
|
|
39
|
+
## 모듈
|
|
40
|
+
|
|
41
|
+
| 모듈 | 의존성 | 공개 API |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `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`) |
|
|
44
|
+
| `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) 과 정확히 동기 (테스트로 강제) |
|
|
45
|
+
| `rscc_common.logging` | 없음 | `configure_logging(log_dir, level, app_name, *, noisy_loggers=..., quiet_uvicorn=...)` — 콘솔 + 자정 롤링 파일(전날분 gzip, 30일 보관). 명시 호출형 (임포트 부작용 없음), 재호출 시 기존 핸들러 close 후 교체 |
|
|
46
|
+
| `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` |
|
|
47
|
+
| `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)) |
|
|
48
|
+
| `rscc_common.text.chosung` | 없음 | `of(s)` — 한글 초성 변환 (예: `"검색 엔진"` → `"ㄱㅅㅇㅈ"`), `is_chosung_query(s)` — 초성 질의 여부. search-api `Chosung.java` / ai-core `chosung.py` 와 골든 벡터 파리티 |
|
|
49
|
+
|
|
50
|
+
## 사용 예
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from fastapi import FastAPI
|
|
54
|
+
import httpx
|
|
55
|
+
|
|
56
|
+
from rscc_common import configure_logging, install_trace_middleware, trace_request_hook
|
|
57
|
+
from rscc_common.errors import ResultCode
|
|
58
|
+
from rscc_common.response import CommonResponse # [fastapi] extra 필요
|
|
59
|
+
|
|
60
|
+
configure_logging(log_dir="logs", app_name="ai-core",
|
|
61
|
+
noisy_loggers=("httpx", "httpcore", "urllib3"), quiet_uvicorn=True)
|
|
62
|
+
|
|
63
|
+
app = FastAPI()
|
|
64
|
+
install_trace_middleware(app) # X-Trace-Id 수신 재사용 → contextvar → 응답 에코
|
|
65
|
+
|
|
66
|
+
# 다운스트림 전파 (trace.md 4단계)
|
|
67
|
+
client = httpx.Client(event_hooks={"request": [trace_request_hook]})
|
|
68
|
+
|
|
69
|
+
@app.get("/thing")
|
|
70
|
+
def get_thing():
|
|
71
|
+
return CommonResponse.ok({"id": 1}).to_wire() # data 포함
|
|
72
|
+
|
|
73
|
+
@app.get("/missing")
|
|
74
|
+
def missing():
|
|
75
|
+
return CommonResponse.fail(ResultCode.NOT_FOUND).to_wire() # data 키 부재
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
요청 컨텍스트 밖(백그라운드 태스크 등)에서 traceId 를 수동 바인딩할 때:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from rscc_common import new_trace_id, reset_trace_id, set_trace_id
|
|
82
|
+
|
|
83
|
+
token = set_trace_id(new_trace_id()) # 또는 요청에서 캡처해 둔 traceId
|
|
84
|
+
try:
|
|
85
|
+
do_background_work() # 이 안의 로그·httpx 발신에 traceId 가 실린다
|
|
86
|
+
finally:
|
|
87
|
+
reset_trace_id(token) # 이전 값 복원 (권장) — 일회성 태스크는 set_trace_id(None) 도 가능
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 알려진 제약
|
|
91
|
+
|
|
92
|
+
- **트레이스 미들웨어는 미처리 예외의 500 응답에 `X-Trace-Id` 를 에코하지 못한다** —
|
|
93
|
+
`app.add_middleware` 등록이 Starlette `ServerErrorMiddleware` 안쪽에서 실행되기 때문.
|
|
94
|
+
`HTTPException`·검증 오류 등 예외 핸들러가 처리하는 경로(4xx 등)는 정상 에코된다.
|
|
95
|
+
상세·우회책은 `rscc_common.trace.install_trace_middleware` docstring 참조.
|
|
96
|
+
- `rscc_common.response` 는 **`[fastapi]` extra 설치 후에만 임포트 가능** —
|
|
97
|
+
기본 설치에서 임포트하면 `ModuleNotFoundError` (pydantic 부재). 나머지 모듈은 기본 설치로 동작.
|
|
98
|
+
- FastAPI 핸들러에서 `CommonResponse` 모델을 **직접 반환하지 말고 반드시 `.to_wire()`** 를
|
|
99
|
+
반환할 것 — 직접 반환하면 `data: null` 이 와이어에 실려 "실패/무데이터 시 data 키 생략" 계약이 깨진다.
|
|
100
|
+
- `configure_logging` 은 **단일 프로세스**(단일 uvicorn worker) 전제 — 멀티 worker 가 같은
|
|
101
|
+
로그 파일을 회전시키면 안전하지 않다. `level` 기본값은 `logging.DEBUG` — 프로덕션은
|
|
102
|
+
`logging.INFO` 권장.
|
|
103
|
+
- `text.chosung.of()` 의 공백 스킵은 스페이스/탭만 (개행·전각 공백은 통과) —
|
|
104
|
+
Java `Chosung.java` 와의 파리티 제약.
|
|
105
|
+
|
|
106
|
+
## 테스트 (저장소 체크아웃에서)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
cd python/rscc-common
|
|
110
|
+
python -m pytest -q # pyproject 의 pythonpath=["src"] 덕에 설치 없이 바로 동작
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
테스트 의존성은 pyproject 의 `[dependency-groups] dev` (pytest, pyyaml, fastapi>=0.110,
|
|
114
|
+
httpx>=0.27) — pip 25.1+ 는 `pip install --group dev`. `tests/test_errors.py` 는 저장소의
|
|
115
|
+
`contracts/error-codes.yaml` 과 enum 정합을 비교하며, 파일이 없는 환경(sdist 등)에서는 자동 skip 된다.
|
|
116
|
+
|
|
117
|
+
## 버전·릴리스
|
|
118
|
+
|
|
119
|
+
- **버전 단일 소스**: `src/rscc_common/__init__.py` 의 `__version__` —
|
|
120
|
+
`pyproject.toml` 은 dynamic version 으로 이 값을 읽는다 (릴리스 시 pyproject 수정 불필요).
|
|
121
|
+
- `py-vX.Y.Z` 태그가 push 되면 GitHub Actions
|
|
122
|
+
([release-python.yml](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/.github/workflows/release-python.yml))
|
|
123
|
+
이 태그↔`__version__` 정합 검증 → `python -m build` → **PyPI Trusted Publishing (OIDC)**
|
|
124
|
+
업로드 → GitHub Release 생성.
|
|
125
|
+
- 절차 상세(runbook)·릴리스 이력:
|
|
126
|
+
[루트 README](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/README.md) ·
|
|
127
|
+
[CHANGELOG.md](https://github.com/Jeonghyeon-Ryu/r-common/blob/master/CHANGELOG.md)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# >=1.26 — 아래 PEP 639 표기(license SPDX 문자열 + license-files 평면 배열)를 지원하는 최소 버전.
|
|
3
|
+
requires = ["hatchling>=1.26"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "rscc-common"
|
|
8
|
+
# version 은 여기 두지 않는다 — src/rscc_common/__init__.py 의 __version__ 이 단일 소스.
|
|
9
|
+
# (두 곳에 적어 두면 릴리스 때 한쪽만 올리는 사고가 나기 쉬워 dynamic 으로 일원화)
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = "RSCC 공통 Python 라이브러리 — 로깅, X-Trace-Id 전파, CommonResponse, ResultCode, 텍스트 유틸"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
# PEP 639 SPDX 라이선스 식별자 — 같은 디렉토리의 LICENSE 파일(MIT)과 쌍으로 배포된다.
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
authors = [{ name = "Jeonghyeon-Ryu" }]
|
|
17
|
+
# 상한을 두지 않는다 — 표준 라이브러리만 쓰는 코어라 새 파이썬(3.14+)에서
|
|
18
|
+
# 설치가 차단될 이유가 없다. 하한 3.11 은 `str | None` 등 문법 사용 때문.
|
|
19
|
+
requires-python = ">=3.11"
|
|
20
|
+
# 기본 설치는 표준 라이브러리만 사용 (의존성 0).
|
|
21
|
+
dependencies = []
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Topic :: Software Development :: Libraries",
|
|
30
|
+
# py.typed 마커 동봉 — 소비 측 mypy/pyright 가 타입 힌트를 읽는다
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/Jeonghyeon-Ryu/r-common"
|
|
36
|
+
Repository = "https://github.com/Jeonghyeon-Ryu/r-common"
|
|
37
|
+
Changelog = "https://github.com/Jeonghyeon-Ryu/r-common/releases"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
# FastAPI 통합(response.py 의 pydantic 포함)과 httpx 훅 실사용에 필요.
|
|
41
|
+
fastapi = ["fastapi>=0.110", "httpx>=0.27"]
|
|
42
|
+
|
|
43
|
+
# 개발/테스트 전용 의존성 (PEP 735) — 배포본 의존성 아님.
|
|
44
|
+
# 설치: `pip install --group dev` (pip 25.1+) 또는 개별 pip install.
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = ["pytest", "pyyaml", "fastapi>=0.110", "httpx>=0.27"]
|
|
47
|
+
|
|
48
|
+
# __version__ 을 빌드 시점에 읽어 패키지 버전으로 쓴다 (위 dynamic 과 쌍).
|
|
49
|
+
[tool.hatch.version]
|
|
50
|
+
path = "src/rscc_common/__init__.py"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/rscc_common"]
|
|
54
|
+
|
|
55
|
+
# src 레이아웃이라 설치 없이는 rscc_common 을 임포트할 수 없다 —
|
|
56
|
+
# pythonpath 로 src 를 얹어 체크아웃 직후 `python -m pytest` 가 바로 돌게 한다.
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
pythonpath = ["src"]
|
|
59
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""rscc-common — RSCC 공통 Python 라이브러리.
|
|
2
|
+
|
|
3
|
+
기본 설치는 표준 라이브러리만 사용한다. 여기서 재노출하는 심볼은 모두 무의존 모듈의 것이다.
|
|
4
|
+
`rscc_common.response` (pydantic 필요) 와 `rscc_common.handlers` (fastapi 필요) 는
|
|
5
|
+
`rscc-common[fastapi]` extra 설치 후 서브모듈로 직접 임포트한다 — 패키지 임포트만으로
|
|
6
|
+
pydantic/fastapi 를 요구하지 않기 위해 여기서 재노출하지 않는다.
|
|
7
|
+
|
|
8
|
+
무의존 유틸 모듈(jwt/datetimes/masking/sse)은 서브모듈 네임스페이스로 재노출한다
|
|
9
|
+
(`from rscc_common import jwt; jwt.decode_token(...)`). 함수가 많고 이름이 일반적이라
|
|
10
|
+
플랫 재노출 대신 네임스페이스로 묶었다.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from . import (
|
|
14
|
+
api_key,
|
|
15
|
+
bizno,
|
|
16
|
+
bulkhead,
|
|
17
|
+
cache,
|
|
18
|
+
circuit_breaker,
|
|
19
|
+
client_ip,
|
|
20
|
+
constant_time,
|
|
21
|
+
datetimes,
|
|
22
|
+
jwt,
|
|
23
|
+
log_sanitize,
|
|
24
|
+
masking,
|
|
25
|
+
metrics,
|
|
26
|
+
rate_limit,
|
|
27
|
+
retry,
|
|
28
|
+
sse,
|
|
29
|
+
validation,
|
|
30
|
+
)
|
|
31
|
+
from .access_log import install_access_log
|
|
32
|
+
from .errors import CommonException, ResultCode
|
|
33
|
+
from .logging import configure_logging
|
|
34
|
+
from .rate_limit import install_rate_limit
|
|
35
|
+
from .security_headers import install_security_headers
|
|
36
|
+
from .trace import (
|
|
37
|
+
async_trace_request_hook,
|
|
38
|
+
get_trace_id,
|
|
39
|
+
install_trace_middleware,
|
|
40
|
+
new_trace_id,
|
|
41
|
+
reset_trace_id,
|
|
42
|
+
set_trace_id,
|
|
43
|
+
trace_request_hook,
|
|
44
|
+
)
|
|
45
|
+
from .user_context import (
|
|
46
|
+
UserContext,
|
|
47
|
+
async_user_context_request_hook,
|
|
48
|
+
get_user_context,
|
|
49
|
+
install_user_context_middleware,
|
|
50
|
+
reset_user_context,
|
|
51
|
+
set_user_context,
|
|
52
|
+
user_context_request_hook,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# 패키지 버전의 단일 소스 — pyproject.toml 은 dynamic version 으로 이 값을 읽는다.
|
|
56
|
+
# 릴리스 절차: 이 값 수정 → py-vX.Y.Z 태그 push (pyproject 는 건드릴 것 없음).
|
|
57
|
+
__version__ = "0.2.0"
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"CommonException",
|
|
61
|
+
"ResultCode",
|
|
62
|
+
"UserContext",
|
|
63
|
+
"api_key",
|
|
64
|
+
"async_trace_request_hook",
|
|
65
|
+
"async_user_context_request_hook",
|
|
66
|
+
"bizno",
|
|
67
|
+
"bulkhead",
|
|
68
|
+
"cache",
|
|
69
|
+
"circuit_breaker",
|
|
70
|
+
"client_ip",
|
|
71
|
+
"configure_logging",
|
|
72
|
+
"constant_time",
|
|
73
|
+
"datetimes",
|
|
74
|
+
"get_trace_id",
|
|
75
|
+
"get_user_context",
|
|
76
|
+
"install_access_log",
|
|
77
|
+
"install_rate_limit",
|
|
78
|
+
"install_security_headers",
|
|
79
|
+
"install_trace_middleware",
|
|
80
|
+
"install_user_context_middleware",
|
|
81
|
+
"jwt",
|
|
82
|
+
"log_sanitize",
|
|
83
|
+
"masking",
|
|
84
|
+
"metrics",
|
|
85
|
+
"new_trace_id",
|
|
86
|
+
"rate_limit",
|
|
87
|
+
"reset_trace_id",
|
|
88
|
+
"reset_user_context",
|
|
89
|
+
"retry",
|
|
90
|
+
"set_trace_id",
|
|
91
|
+
"set_user_context",
|
|
92
|
+
"sse",
|
|
93
|
+
"trace_request_hook",
|
|
94
|
+
"user_context_request_hook",
|
|
95
|
+
"validation",
|
|
96
|
+
"__version__",
|
|
97
|
+
]
|