Skip to main content

rscc-common

RSCC 공통 Python 라이브러리. 응답 봉투, 에러 코드, X-Trace-Id 전파, ASGI 미들웨어, 회복탄력성(재시도·서킷 브레이커·격벽·TTL 캐시), 보안(JWT·AES-GCM·웹훅 서명·로그 리댁션), 한국 도메인 유틸을 제공한다.

  • 기본 설치는 의존성 0 (표준 라이브러리만). FastAPI/pydantic 통합과 암호화는 extra 로 opt-in.
  • Java(com.rscc:rscc-common-*)·JS(@rscc/common-*) 구현과 와이어 계약(응답 봉투·에러 코드· 헤더 규약 등)과 공유 테스트 벡터를 맞춘 폴리글랏 라이브러리의 Python 구현이다. 계약의 핵심 규칙은 각 모듈 docstring 에 요약돼 있어 이 패키지만으로 사용할 수 있다.
  • Python >= 3.11 (상한 없음), 타입 힌트 포함(py.typed), 라이선스 MIT.
  • 시간 단위는 초 (Java/JS 는 ms — 초 × 1000 == ms).

설치

pip install rscc-common                    # 기본 — 의존성 0
pip install "rscc-common[fastapi]"         # + fastapi>=0.110, pydantic>=2, httpx>=0.27
pip install "rscc-common[crypto]"          # + cryptography>=42 (AES-256/GCM)
pip install "rscc-common[fastapi,crypto]"  # 둘 다
extra 설치되는 것 이 extra 가 있어야 임포트되는 모듈
(없음) — 아래 표에서 * 표시가 없는 모든 모듈
fastapi fastapi>=0.110, pydantic>=2 (v2 전용 API 사용), httpx>=0.27 response, handlers, health_fastapi
crypto cryptography>=42 (네이티브 휠) crypto

extra 없이 해당 모듈을 임포트하면 그 모듈만 ImportError/ModuleNotFoundError 이고, 패키지 자체(import rscc_common)와 나머지 모듈은 계속 동작한다. 순수 ASGI 미들웨어 (install_exception_handlers 를 제외한 install_*)와 httpx 훅은 fastapi/httpx 를 임포트하지 않으므로 기본 설치로도 임포트된다.

PyPI 대신 git 태그로 직설치할 수도 있다 (X.Y.Z 는 설치할 릴리스 버전):

pip install "rscc-common[fastapi] @ git+https://github.com/Jeonghyeon-Ryu/r-common.git@py-vX.Y.Z#subdirectory=python/rscc-common"

모듈 개요

* = extra 필요. 무의존 모듈은 패키지 최상위에서 서브모듈 네임스페이스로도 쓸 수 있고 (from rscc_common import jwt), 자주 쓰는 심볼은 최상위에 플랫 재노출된다 (configure_logging, TraceIdFilter, install_* 계열, get_trace_id/set_trace_id/ reset_trace_id/new_trace_id, trace_request_hook, UserContext/get_user_context, ResultCode, CommonException 등).

웹·와이어 계약 (ASGI / FastAPI)

모듈 주요 API
trace X-Trace-Id 규약 — install_trace_middleware(app)(수신 값 [A-Za-z0-9._-]{1,64} 이면 재사용, 아니면 8자리 hex 생성 → contextvar 바인딩 → 응답 에코), get_trace_id/set_trace_id/reset_trace_id/new_trace_id, httpx 발신 훅 trace_request_hook/async_trace_request_hook
user_context 내부 X-User-* 헤더 → UserContext contextvar — install_user_context_middleware(app), get_user_context(), httpx 훅 user_context_request_hook
errors ResultCode enum(.code 문자열 / .http_status / .message), 비즈니스 예외 CommonException(result_code, custom_message=None)
response * CommonResponse 봉투(success/code/message/data) — ok(data)/fail(result_code, message=None, data=None), to_wire()(data 없으면 키 생략), WireDateTime, BulkResultModel
handlers * install_exception_handlers(app, *, validation_to_400=True) — CommonException·검증 오류(기본 400)·HTTPException·catch-all 500 을 실패 봉투로 변환
validation flatten_loc(loc) — pydantic loc → 필드 경로(items[0].name)
access_log install_access_log(app, *, headers=(), include_query_string=False, slow_threshold_ms=None) — 요청당 1라인, 민감 헤더 마스킹
security_headers install_security_headers(app, *, hsts=False) — 보안 응답 헤더 프리셋(앱이 실은 헤더 우선)
rate_limit install_rate_limit(app, *, capacity=20, refill_per_second=10.0) — IP 키별 429 게이트, TokenBucket
client_ip resolve_client_ip/resolve_from_scope(scope, trusted_hops=…/trusted_proxies=…) — 안전 기본값 0홉(전달 헤더 무시)
idempotency install_idempotency(app, store=None, …) — Idempotency-Key 중복 시 409, IdempotencyStore Protocol / InMemoryIdempotencyStore
bulk 부분 실패 봉투 — BulkResultBuilder, BulkResult/BulkItem, to_wire/from_wire
query parse_sort(허용 목록 밖 → 400) / parse_page(관용 클램프)
upload validate_upload(filename, size, head, *, max_size_bytes, allowed_extensions) — 크기→확장자→매직바이트 3중 검증, sniff
health / health_fastapi * HealthRegistry, CheckResult / create_health_router(registry) — liveness·readiness(DOWN → 503)
sse SSE 프레임 빌더 — frame_delta/frame_sources/frame_error/frame_done 등
datetimes 오프셋 없는 와이어 datetime — to_wire_datetime/parse_wire_datetime(Z·오프셋은 변환 없이 버림)/strip_zone
feature_flags FeatureFlags(source) + is_enabled(key) — 참 집합 {"true","1","on","yes"}, RSCC_FLAG_* 환경변수 폴백
metrics 표준 메트릭·라벨 이름 문자열 상수 (계측 바인딩 없음)

회복탄력성

모듈 주요 API
retry retry_call(fn, *, retries=3, should_retry=…, override_delay=…, max_elapsed=…) / retry_call_async — 지수 백오프 + full jitter, parse_retry_after, is_retryable_status
circuit_breaker CircuitBreaker(failure_threshold=, open_duration=) — call(fn, *args, **kwargs) / await call_async(fn, …) 권장(게이트 + 보고 1회 보장, 차단 시 CircuitOpenError), 수동 allow_request/on_success/on_failure/on_ignore, CircuitState
bulkhead Bulkhead(max_concurrent=, max_queue=0)(with) / AsyncBulkhead(async with) — 만석 즉시 BulkheadFullError
cache TtlCache(ttl=, max_entries=) / AsyncTtlCache — get(key, loader) single-flight(동시 호출은 로더 1회 공유, 실패·None 비캐시)
rate_limit TokenBucket(capacity=, refill_per_second=) — try_acquire(n=1) 부족 시 대기 없이 False

로깅·보안

모듈 주요 API
logging configure_logging(log_dir, level, app_name, *, include_trace_id=True, clear_root_handlers=False, per_pid_file=False, quiet_uvicorn=False, noisy_loggers=()) — 콘솔 + 자정 롤링 파일(gzip, 30일), 로그 라인에 traceId. TraceIdFilter — 자체 로깅 구성용
log_redact install_secret_redaction() / redact_secrets(s) — Bearer/JWT/API 키/주민번호/카드번호 → [REDACTED:<type>]
log_sanitize sanitize_log_value(value, max_length=…) — 제어 문자 치환 + 절단(로그 인젝션 방지)
jwt HS256 — generate_token(user_id, secret, role=None, *, kid=None), decode_token(token, secret=None, *, keys=None)(kid 로테이션), extract_subject/extract_role, JwtError
crypto * encrypt(plain, key) / decrypt(cipher, key) — AES-256/GCM, Base64(IV‖암호문‖태그), 실패 시 CryptoError
webhook sign(secret, payload) → t=…,v1=… / verify(secrets, header, payload) — HMAC-SHA256, 다중 시크릿 로테이션, 스큐 300초
api_key issue("live")(또는 "test"), is_well_formed, checksum_ok, sha256_hex — rscc_live_… 48자 키
constant_time compare_digest — 시크릿·해시 상수시간 비교
masking mask_name/mask_phone/mask_email/mask_card_number/mask_secret

한국 도메인 유틸

모듈 주요 API
rrn is_valid_rrn(형식·성별코드·생년월일, 체크섬 미포함), is_foreigner_rrn, birth_date_of, checksum_ok_legacy
bizno is_valid_business_number(사업자 10자리) / is_valid_corporate_number(법인 13자리)
phone normalize_phone, classify_phone(mobile/landline/…), format_phone, to_e164
age age_man(만 나이) / age_by_year(연 나이) / age_insurance(보험 나이)
money to_korean_words, to_formal_notation, abbreviate_amount(예: 1.2억)
business_days BusinessDays(holidays=…) — 영업일 가감·판정(공휴일 주입형, 내장 테이블 없음)
text.chosung / text.jamo / text.josa 초성 변환 of·is_chosung_query / 자모 분해·결합·혼합 질의 matches / 조사 선택 pick·attach

빠른 시작

import logging

import httpx
from fastapi import FastAPI

from rscc_common import configure_logging, install_trace_middleware, trace_request_hook
from rscc_common.errors import CommonException, ResultCode
from rscc_common.handlers import install_exception_handlers   # [fastapi]
from rscc_common.response import CommonResponse               # [fastapi]

configure_logging(log_dir="logs", level=logging.INFO, app_name="order-api", quiet_uvicorn=True)
# → [2026-09-26 12:00:00] INFO  [3f9a1c2e] order_api - 주문 조회: 7
#   (traceId 는 요청 컨텍스트 밖이면 "-". uvicorn --workers > 1 이면 per_pid_file=True 필수)

app = FastAPI()
install_trace_middleware(app)      # X-Trace-Id 수신 재사용/생성 → contextvar → 응답 에코
install_exception_handlers(app)    # 예외 → 실패 봉투 (CommonException·검증 400·HTTPException·500)

client = httpx.Client(event_hooks={"request": [trace_request_hook]})  # 다운스트림 전파
log = logging.getLogger("order_api")

@app.get("/orders/{order_id}")
def get_order(order_id: int):
    log.info("주문 조회: %s", order_id)
    if order_id > 1000:
        raise CommonException(ResultCode.NOT_FOUND)             # → 404 + {"success": false, "code": "404", ...}
    return CommonResponse.ok({"id": order_id}).to_wire()        # 반드시 to_wire() 반환

회복탄력성 — 재시도 안에서 서킷 브레이커로 게이트하고, OPEN 차단은 재시도하지 않는다:

from rscc_common.circuit_breaker import CircuitBreaker, CircuitOpenError
from rscc_common.retry import retry_call

cb = CircuitBreaker(failure_threshold=5, open_duration=30.0)

data = retry_call(
    lambda attempt: cb.call(fetch_inventory, item_id),   # 비동기는 await cb.call_async(afetch, item_id)
    should_retry=lambda exc, attempt: not isinstance(exc, CircuitOpenError),
)

call/call_async 는 정상 반환 → 성공, Exception → 실패(같은 예외 재전파), 취소·인터럽트 등 그 밖의 BaseException → 판정 없는 반납(on_ignore)으로 정확히 1회 보고한다.

요청 밖(백그라운드 태스크 등)에서 traceId 잇기:

from rscc_common import new_trace_id, reset_trace_id, set_trace_id

token = set_trace_id(captured_trace_id or new_trace_id())
try:
    do_background_work()     # 이 안의 로그 라인·httpx 발신에 traceId 가 실린다
finally:
    reset_trace_id(token)

자체 로깅 구성(dictConfig 등)을 쓰는 앱은 configure_logging 대신 TraceIdFilter 를 핸들러에 붙이고 포맷에 %(trace_id)s 를 넣는다:

from rscc_common.logging import TraceIdFilter

handler = logging.StreamHandler()
handler.addFilter(TraceIdFilter())    # dictConfig: {"()": "rscc_common.logging.TraceIdFilter"}
handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)-5s [%(trace_id)s] %(name)s - %(message)s"))

알려진 제약

  • 미처리 예외의 500 응답과 X-Trace-Id — 트레이스 미들웨어는 Starlette ServerErrorMiddleware 안쪽에서 실행되므로 500 응답에 에코하지 못한다. install_exception_handlers 의 catch-all 이 수신 X-Trace-Id 가 유효하면 그 값을 500 응답 헤더에 직접 에코해 부분 보완한다. 수신 값이 없어 미들웨어가 새로 생성한 경우엔 500 응답에 헤더가 없고, catch-all 의 에러 로그 라인도 traceId 가 - 로 찍힌다(그 시점엔 contextvar 가 이미 정리됨). HTTPException·검증 오류 등 핸들러 처리 경로(4xx)는 정상 에코된다.
  • CommonResponse 모델을 FastAPI 핸들러에서 직접 반환하지 말고 .to_wire() 를 반환할 것 — 직접 반환하면 data: null 이 실려 "데이터 없으면 키 생략" 계약이 깨진다.
  • configure_logging
    • 기본 파일명({app_name}.log)은 프로세스 1개 전제 — uvicorn --workers > 1 등 멀티 프로세스면 per_pid_file=True ({app_name}.{pid}.log) 필수.
    • 재호출 시 자신이 부착한 핸들러만 교체하고 다른 루트 핸들러(OTel·Sentry 등)는 보존한다 (clear_root_handlers=True 면 전부 제거 — 이전 버전의 동작). 기본 포맷에 [traceId] 가 들어간다(include_trace_id=False 로 이전 포맷).
    • level 기본값은 logging.DEBUG — 운영은 logging.INFO 권장.
    • install_secret_redaction() 은 configure_logging 이후에 호출(재구성마다 다시 호출).
  • 인메모리 상태(install_rate_limit, install_idempotency 기본 스토어, TtlCache, CircuitBreaker)는 프로세스(워커) 단위다 — 인스턴스 간 공유가 필요하면 분산 스토어를 IdempotencyStore Protocol 로 구현해 store= 로 주입한다.
  • CircuitBreaker 를 수동 게이트(allow_request + 보고)로 쓸 때 보고가 누락되면 HALF_OPEN 프로브가 풀리지 않는다 — call/call_async 를 쓰면 보고가 보장된다.
  • jwt 는 HS256 시크릿이 32바이트(UTF-8) 미만이면 ValueError (fail-fast, allow_weak_secret=True 는 교체 전 임시 옵트아웃).
  • log_redact 는 정규식 기반 best-effort — 미탐·오탐이 공존한다. log_sanitize 와 병용 시 반드시 redact → sanitize(절단) 순서.
  • rrn.is_valid_rrn 은 체크섬을 검사하지 않는다 — 2020-10 이후 발급분은 뒷자리가 임의번호라 mod-11 이 성립하지 않는다(checksum_ok_legacy 는 그 이전 발급분 전용). 저장 시 암호화 의무.
  • text.chosung.of() 의 공백 스킵은 스페이스/탭만(Java 파리티).

버전·릴리스

버전의 단일 소스는 rscc_common.__version__ 이다. py-vX.Y.Z 태그 push 시 CI 가 태그와 버전 정합을 검증한 뒤 PyPI 에 배포한다(Trusted Publishing). 변경 이력은 소스 저장소의 CHANGELOG.md 에 있다.

Metadata

Release files for rscc-common 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rscc-common 0.4.0
File Size Uploaded Provenance
rscc_common-0.4.0.tar.gz 156.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for rscc-common 0.4.0
File Interpreter ABI Platform
rscc_common-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 277.3 kB

Release files / rscc_common-0.4.0.tar.gz

Download URL rscc_common-0.4.0.tar.gz
Size 156.5 kB
Tags Source
SHA-256 checksum
How to use checksums
dd681b1cc378d4a459a9f748f9dd1da0203f954239141b6b25e416ff83f8c835
BLAKE2b-256 checksum
How to use checksums
8ffa5f8def613d02b63180e0389810413064c2ccef31775dec6a4faafabd741c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release files / rscc_common-0.4.0-py3-none-any.whl

Download URL rscc_common-0.4.0-py3-none-any.whl
Size 120.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dd153a11e12c87218539989efdf6d982c127ed4c7c44c82fdfc613c8bef1962b
BLAKE2b-256 checksum
How to use checksums
76237034733ed9d9e54ce18d4a94bfe933785ca9144cf4546715370798393740
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Security for rscc-common 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rscc_common-0.4.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page