sg-word-filter-python 1.0.1
금칙어 필터링 모듈 - 데이터 주입 방식
License: MIT
Requires Python: >=3.7
Keywords: filter, banned, words, blocklist

# sg-word-filter-python

[![PyPI version](https://img.shields.io/pypi/v/sg-word-filter-python.svg)](https://pypi.org/project/sg-word-filter-python/)
[![Python Versions](https://img.shields.io/pypi/pyversions/sg-word-filter-python.svg)](https://pypi.org/project/sg-word-filter-python/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

고성능 금칙어 필터링 모듈 (Python) - 정확 일치 및 부분 문자열 검색 지원

## ✨ 특징

- ⚡ **빠른 검색**: Set 기반 O(1) 정확 일치 + Aho-Corasick 부분 문자열 검색
- 🔒 **싱글톤 패턴**: 앱 라이프사이클 동안 단 한 번만 초기화
- 🛡️ **스레드 안전**: Double-checked locking으로 멀티스레드 환경 지원
- 🌐 **다국어 지원**: UTF-8 완벽 지원 (한글, 영어, 일본어 등)
- 📦 **경량**: 핵심 기능만 포함 (~15KB)
- 🧪 **완벽한 테스트**: 100% 코드 커버리지
- 🔤 **타입 힌트**: 완전한 타입 어노테이션 포함

## 📦 설치

### PyPI (공개 레지스트리)

```bash
pip install sg-word-filter-python
```

### GitHub (인하우스)

#### Git에서 직접 설치

```bash
# 최신 버전
pip install git+https://github.com/yourorg/sg-word-filter-python.git

# 특정 버전/태그
pip install git+https://github.com/yourorg/sg-word-filter-python.git@v1.0.0

# 특정 브랜치
pip install git+https://github.com/yourorg/sg-word-filter-python.git@main
```

#### GitHub Releases에서 Wheel 설치

```bash
pip install https://github.com/yourorg/sg-word-filter-python/releases/download/v1.0.0/sg_word_filter-1.0.0-py3-none-any.whl
```

#### requirements.txt에 추가

```txt
# Git repository
sg-word-filter-python @ git+https://github.com/yourorg/sg-word-filter-python.git@v1.0.0

# 또는 Release wheel
sg-word-filter-python @ https://github.com/yourorg/sg-word-filter-python/releases/download/v1.0.0/sg_word_filter-1.0.0-py3-none-any.whl
```

자세한 배포 가이드는 [DEPLOY.md](./DEPLOY.md)를 참조하세요.

## 🚀 빠른 시작

### 1. 금칙어 파일 준비 (`config/blocklist.json`)

```json
["나쁜말", "욕설", "비속어", "금지어"]
```

### 2. 모듈 초기화 및 사용

```python
import sg_word_filter

# 애플리케이션 시작 시 초기화 (단 한 번만!)
sg_word_filter.init('./config/blocklist.json')

# ===== 정확 일치 검색 =====
print(sg_word_filter.is_forbidden('나쁜말'))  # True
print(sg_word_filter.is_forbidden('정상단어'))  # False

# 텍스트에서 금칙어 찾기
words = sg_word_filter.find_forbidden_words('이것은 나쁜말 과 욕설 입니다')
print(words)  # ['나쁜말', '욕설']

# 금칙어 포함 여부만 확인
has_bad = sg_word_filter.has_forbidden_words('이것은 나쁜말입니다')
print(has_bad)  # True

# ===== 부분 문자열 검색 (Aho-Corasick) =====
results = sg_word_filter.find_forbidden_substrings('나쁜말과욕설입니다')
print(results)
# [
#   {'word': '나쁜말', 'index': 0},
#   {'word': '욕설', 'index': 5}
# ]

# 부분 문자열 포함 여부
has_substring = sg_word_filter.has_forbidden_substrings('나쁜것')
print(has_substring)  # True (금칙어 목록에 "나쁜"이 있다면)
```

## 📖 API 레퍼런스

### 초기화 및 상태 관리

#### `init(filepath, *, silent=False)`

금칙어 목록을 파일에서 로드하여 초기화합니다. 앱 라이프사이클 중 **단 한 번만** 호출되어야 합니다.

```python
sg_word_filter.init('./config/blocklist.json', silent=True)
```

**Parameters:**
- `filepath` (str): JSON 배열 형태의 금칙어 파일 경로
- `silent` (bool, keyword-only): True일 경우 콘솔 로그 출력 억제 (기본값: False)

**Raises:**
- `FilterError`: 파일을 찾을 수 없거나, JSON 형식이 잘못되었거나, 배열이 아닌 경우

#### `get_status()`

모듈의 현재 상태 정보를 반환합니다.

```python
status = sg_word_filter.get_status()
print(status)
# {
#   'initialized': True,
#   'count': 10,
#   'file_path': '/absolute/path/to/blocklist.json',
#   'loaded_at': '2025-11-18T10:30:00.000000'
# }
```

**Returns:** `dict` - `{'initialized', 'count', 'file_path', 'loaded_at'}`

#### `reset()`

모듈을 초기화 이전 상태로 되돌립니다. 주로 테스트 용도로 사용됩니다.

```python
sg_word_filter.reset()
```

### 정확 일치 검색

#### `is_forbidden(word)`

단어가 금칙어 목록에 **정확히 일치**하는지 확인합니다.

```python
sg_word_filter.is_forbidden('나쁜말')  # True
sg_word_filter.is_forbidden('나쁜')    # False (부분 일치는 False)
```

**Parameters:**
- `word` (str): 확인할 단어

**Returns:** `bool`

#### `find_forbidden_words(text, *, delimiter=None)`

텍스트를 단어 단위로 분리하여 금칙어를 찾습니다.

```python
words = sg_word_filter.find_forbidden_words('나쁜말 과 욕설')
# ['나쁜말', '욕설']

# 사용자 정의 구분자
words2 = sg_word_filter.find_forbidden_words('나쁜말,욕설,정상', delimiter=',')
# ['나쁜말', '욕설']

# 정규표현식 구분자
import re
words3 = sg_word_filter.find_forbidden_words('나쁜말.욕설!정상', delimiter=re.compile(r'[.,!]'))
```

**Parameters:**
- `text` (str): 검색할 텍스트
- `delimiter` (str|re.Pattern, keyword-only): 단어 구분자 (기본값: `r'\s+'`)

**Returns:** `List[str]` - 발견된 금칙어 리스트

#### `has_forbidden_words(text, *, delimiter=None)`

텍스트에 금칙어가 포함되어 있는지 확인합니다.

```python
sg_word_filter.has_forbidden_words('이것은 나쁜말입니다')  # True
```

**Parameters:**
- `text` (str): 검색할 텍스트
- `delimiter` (str|re.Pattern, keyword-only): `find_forbidden_words()`와 동일

**Returns:** `bool`

### 부분 문자열 검색 (Aho-Corasick)

#### `find_forbidden_substrings(text, *, case_sensitive=False)`

Aho-Corasick 알고리즘을 사용하여 텍스트에서 금칙어를 **부분 문자열**로 검색합니다.

```python
# 금칙어 목록: ['나쁜', '욕']
results = sg_word_filter.find_forbidden_substrings('나쁜말과 욕설입니다')
# [
#   {'word': '나쁜', 'index': 0},
#   {'word': '욕', 'index': 6}
# ]

# 대소문자 구분
results2 = sg_word_filter.find_forbidden_substrings('Bad WORST', case_sensitive=True)
```

**Parameters:**
- `text` (str): 검색할 텍스트
- `case_sensitive` (bool, keyword-only): 대소문자 구분 여부 (기본값: False)

**Returns:** `List[Dict[str, Any]]` - `[{'word': str, 'index': int}, ...]`

**시간 복잡도:** O(n + m) - n: 텍스트 길이, m: 매칭 결과 수

#### `has_forbidden_substrings(text, *, case_sensitive=False)`

텍스트에 금칙어 부분 문자열이 포함되어 있는지 확인합니다.

```python
# 금칙어 목록: ['나쁜']
sg_word_filter.has_forbidden_substrings('나쁜말')  # True
sg_word_filter.has_forbidden_substrings('좋은말')  # False
```

**Parameters:**
- `text` (str): 검색할 텍스트
- `case_sensitive` (bool, keyword-only): `find_forbidden_substrings()`와 동일

**Returns:** `bool`

### 예외 클래스

#### `FilterError`

모듈에서 발생하는 모든 예외는 `FilterError` 클래스를 사용합니다.

```python
try:
    sg_word_filter.init('/invalid/path.json')
except sg_word_filter.FilterError as e:
    print(f'에러 코드: {e.code}')
    print(f'메시지: {e}')
```

**에러 코드:**
- `FILE_NOT_FOUND`: 파일을 찾을 수 없음
- `INVALID_JSON`: JSON 파싱 실패
- `INVALID_FORMAT`: 배열 형태가 아님
- `NOT_INITIALIZED`: 초기화되지 않은 상태에서 함수 호출
- `INVALID_INPUT`: 잘못된 입력 타입
- `UNEXPECTED_ERROR`: 예상치 못한 오류

## 🎯 사용 사례

### FastAPI 애플리케이션

```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import sg_word_filter

app = FastAPI()

# 앱 시작 시 초기화
@app.on_event("startup")
async def startup_event():
    sg_word_filter.init('./config/blocklist.json')

class Comment(BaseModel):
    text: str

@app.post("/comment")
async def create_comment(comment: Comment):
    bad_words = sg_word_filter.find_forbidden_words(comment.text)
    
    if bad_words:
        raise HTTPException(
            status_code=400,
            detail={
                'error': '금칙어가 포함되어 있습니다',
                'words': bad_words
            }
        )
    
    return {'success': True}
```

### Django 미들웨어

```python
# middleware.py
import sg_word_filter

class ProfanityFilterMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        sg_word_filter.init('./config/blocklist.json', silent=True)

    def __call__(self, request):
        if request.method == 'POST' and request.POST.get('comment'):
            comment = request.POST['comment']
            
            # 부분 문자열 검색으로 우회 시도 방지
            violations = sg_word_filter.find_forbidden_substrings(comment)
            
            if violations:
                from django.http import JsonResponse
                return JsonResponse({
                    'error': '메시지에 부적절한 내용이 포함되어 있습니다',
                    'violations': violations
                }, status=400)
        
        response = self.get_response(request)
        return response
```

### 데코레이터 패턴

```python
import sg_word_filter
from functools import wraps

sg_word_filter.init('./config/blocklist.json', silent=True)

def filter_profanity(func):
    @wraps(func)
    def wrapper(text, *args, **kwargs):
        if sg_word_filter.has_forbidden_words(text):
            raise ValueError('텍스트에 금칙어가 포함되어 있습니다')
        return func(text, *args, **kwargs)
    return wrapper

@filter_profanity
def process_comment(text):
    print(f'댓글 처리: {text}')
    return True

# 사용
process_comment('정상적인 댓글')  # OK
process_comment('나쁜말 포함')    # ValueError 발생
```

## 🔬 알고리즘 비교

| 기능 | 정확 일치 (Set) | 부분 문자열 (Aho-Corasick) |
|------|----------------|---------------------------|
| 시간 복잡도 | O(1) | O(n + m) |
| 공간 복잡도 | O(k) | O(k * avg_len) |
| 사용 케이스 | "나쁜말" 전체 검출 | "나쁜" → "나쁜말", "나쁜것" 검출 |
| 우회 방지 | 약함 | 강함 |

- k: 금칙어 개수
- n: 텍스트 길이
- m: 매칭 결과 수
- avg_len: 평균 금칙어 길이

## 📊 성능

```python
# 벤치마크 (10,000개 금칙어, 1,000자 텍스트)
init():                   ~40ms  (최초 1회만)
is_forbidden():           ~0.001ms (Set lookup)
find_forbidden_words():   ~0.4ms  (텍스트 분리 + Set lookup)
find_forbidden_substrings(): ~1.5ms (Aho-Corasick)
```

## 🧪 테스트

```bash
# 전체 테스트 실행
pytest tests/ -v

# 커버리지 확인
pytest tests/ --cov=sg_word_filter --cov-report=html

# 특정 테스트만 실행
pytest tests/test_filter.py::TestInit -v
```

## 💻 개발 환경 설정

```bash
# Repository 클론
git clone https://github.com/yourorg/sg-word-filter-python.git
cd sg-word-filter-python

# 가상환경 생성 및 활성화
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 개발 모드로 설치
pip install -e ".[dev]"

# 테스트 실행
pytest tests/ -v
```

## 📝 변경 로그

[CHANGELOG.md](./CHANGELOG.md) 참조

## 🤝 기여

이슈 제기 및 풀 리퀘스트를 환영합니다!

1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request

## 📄 라이센스

MIT License - [LICENSE](./LICENSE) 파일 참조

## 👥 관련 프로젝트

- [sg-word-filter-node](https://github.com/yourorg/sg-word-filter-node) - Node.js 버전

## 📞 지원

- 🐛 버그 리포트: [Issues](https://github.com/yourorg/sg-word-filter-python/issues)
- 💬 질문 및 토론: [Discussions](https://github.com/yourorg/sg-word-filter-python/discussions)
- 📧 이메일: dev@yourcompany.com

---

**Made with ❤️ by Your Organization**