Metadata-Version: 2.4
Name: pxa-framework
Version: 0.1.0
Summary: FastAPI 기반 주니어 개발자용 공통 프레임워크 (인증/로깅/예외/DB 내재화)
Author: Daniel Lee with Claude
License-Expression: LicenseRef-Proprietary
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: fastapi==0.128.*
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: SQLAlchemy<2.1,>=2.0
Requires-Dist: pydantic>=2.7
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: redis>=5.0
Requires-Dist: psycopg[binary]>=3.1
Requires-Dist: PyYAML>=6.0
Provides-Extra: mariadb
Requires-Dist: PyMySQL>=1.1; extra == "mariadb"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: flake8>=7.0; extra == "dev"
Requires-Dist: pep8-naming>=0.13; extra == "dev"
Requires-Dist: fakeredis>=2.21; extra == "dev"

# pxa-framework

FastAPI 기반 **주니어 개발자용 공통 프레임워크**. 주니어 개발자는 라이브러리 하나(`pxa-framework`)만 설치하면, 로깅·예외처리·사용자 인증·DB 연결·표준 응답 등 공통기능이 모두 내장된 앱 위에서 **도메인 로직만** 작성하면 됩니다.

```python
from pxa import create_app, ApiResponse, NotFoundError
from pxa.auth import current_user
from pxa.fastapi import APIRouter, Depends      # fastapi 직접 import 금지

app = create_app(authenticator=my_authenticate)   # 공통기능 전부 내장

router = APIRouter(prefix="/items")

@router.get("/{item_id}", response_model=ApiResponse)
def get_item(item_id: int):
    item = find(item_id)
    if item is None:
        raise NotFoundError("없는 항목")     # -> pxa-20004
    return ApiResponse.ok(result=item, message="조회 성공")

app.include_router(router)
```

---

## 1. 핵심 설계 개요

| 요구사항 | 구현 위치 |
|---|---|
| 1. Opaque Token + Redis + HttpOnly 쿠키 | `pxa/auth/` (`session_store.py`, `router.py`, `dependencies.py`) |
| 2. 사용자 DB 저장 + DB 추상화(PostgreSQL↔MariaDB) | `pxa/config.py(DbConfig.url)`, `pxa/db/engine.py` |
| 3. FastAPI 0.128 | `pyproject.toml` (`fastapi==0.128.*`) |
| 4. Set-Cookie 응답헤더 | `pxa/auth/router.py(_set_session_cookie)` |
| 5. HTTP 200 고정 + pxa-1xxxx/2xxxx 코드 | `pxa/codes.py`, `pxa/exceptions.py(_json)` |
| 6. 표준 응답 포맷(성공/메시지/결과) | `pxa/response.py(ApiResponse)` |
| 7. ORM+SQL 혼용, is_valid, 404=pxa-20004, 필수값 검증 | `pxa/db/repository.py`, `pxa/db/base.py`, `pxa/db/sql_loader.py`, `pxa/db/sql/` |
| 8. 트랜잭션 관리(예외 시 rollback) | `pxa/db/engine.py(session_scope)` |
| 9. 커넥션 풀 관리(은닉) | `pxa/db/engine.py(init_engine)` |
| 10. 설정 파일 분리 | `config/config.yaml`, `pxa/config.py` |
| 11. 예외/에러코드/메시지 공통 모듈 | `pxa/exceptions.py`, `pxa/codes.py` |
| 12. 로그 디렉토리 설정 가능 | `pxa/logging_conf.py`, `config.yaml(logging.dir)` |
| 13. Redis 은닉 | `pxa/auth/session_store.py` (개발자 직접 접근 불필요) |
| 14. pytest API 테스트(DB 연결 제외) | `tests/` |
| 15. Nexus/PyPI 패키징 | 본 문서 5장 |
| 16. PEP 네이밍 강제 점검 | `.flake8` (flake8 + pep8-naming) |
| FastAPI 비노출(파사드) | `pxa/fastapi.py` |

---

## 1-1. FastAPI 비노출 — `pxa.fastapi` 파사드

주니어 개발자는 `fastapi` / `pydantic` 을 **직접 import 하지 않습니다.** 필요한 심볼은 모두 `pxa.fastapi` 에서 가져옵니다.

```python
# 잘못된 예 (금지)
from fastapi import APIRouter, Depends
from pydantic import BaseModel

# 올바른 예
from pxa.fastapi import APIRouter, Depends, BaseModel
```

프레임워크가 내부적으로 어떤 웹 프레임워크/버전(FastAPI 0.128)을 쓰는지 사용자 코드와 분리되어, 향후 업그레이드/교체 시에도 주니어 코드는 그대로 둘 수 있습니다. `pxa.fastapi` 재노출 심볼: `APIRouter, Depends, Request, Response, BackgroundTasks, HTTPException, status, Query/Path/Body/Header/Cookie/Form/File/UploadFile`, 응답 타입(`JSONResponse` 등), `BaseModel/Field/field_validator`, `TestClient`.

> CI 에서 fastapi 직접 사용을 차단하려면(파사드 파일만 예외):
> `grep -rnE "^\s*(from|import)\s+fastapi" --include=*.py . | grep -v "src/pxa/fastapi.py"`

---

## 2. 응답 규약 (요구사항 5, 6)

모든 응답의 **HTTP status 는 항상 200**이며, 정상/비정상은 애플리케이션 코드로 구분합니다.

```json
{ "success": true, "code": "pxa-10000", "message": "정상 처리되었습니다.", "result": { } }
```

- `pxa-10000` : 정상
- `pxa-2xxxx` : 비정상 (예: `pxa-20004` 데이터 없음, `pxa-20002` 필수값 누락, `pxa-20003` 미인증)

> 참고: 요구사항 7의 "없는 데이터 → 404"는 **애플리케이션 코드 `pxa-20004`** 로 표현됩니다(요구사항 5에 따라 HTTP 자체는 200 유지). HTTP 404를 그대로 내보내려면 `pxa/codes.py`의 `NOT_FOUND.http_status`와 `exceptions.py(_json)`만 조정하면 됩니다.

에러코드 추가는 `pxa/codes.py`의 `AppCode` Enum 에 한 줄 추가하면 됩니다.

---

## 3. ORM 과 SQL 혼용 (요구사항 7)

- **단일 테이블 CRUD → ORM**: `Repository(Model, session)` 의 `get_or_404 / create / update / delete`. `create/update` 전에 `is_valid()`로 **필수값·정합성을 DB 저장 전에 검증**합니다.
- **복잡한 쿼리 → SQL 파일**: `.sql` 파일을 `pxa/db/sql/`(또는 앱별 디렉토리)에 두고 `SqlRepository.query("파일명", 파라미터=...)` 로 호출합니다. SQL 은 코드와 **디렉토리로 분리**됩니다.

```python
from pxa.db import Repository, SqlRepository, session_scope
from pxa.models import User

with session_scope() as db:          # 트랜잭션: 예외 시 자동 rollback
    repo = Repository(User, db)
    user = repo.get_or_404(1)        # 없으면 pxa-20004

    sql = SqlRepository(db)
    rows = sql.query("example_user_search", keyword="kim", limit=10, offset=0)
```

모델은 `__required__` 로 필수 컬럼을, `validate()` 오버라이드로 정합성 규칙을 선언합니다(`pxa/models/user.py` 참고).

---

## 4. 설정 / 로그 / DB 전환 (요구사항 2, 9, 10, 12)

모든 환경값은 `config/config.yaml` 에 있습니다. 환경변수 `PXA_CONFIG`로 경로 지정, `PXA_DB__HOST` 처럼 개별 override 가능합니다.

- **DB 전환**: `db.dialect` 를 `postgresql` → `mariadb` 로 바꾸고 `pip install "pxa-framework[mariadb]"` 설치만 하면 됩니다. 커넥션 풀(`pool_size` 등)은 프레임워크가 내부 관리하므로 주니어는 몰라도 됩니다.
- **로그 위치**: `logging.dir` 변경으로 저장 디렉토리를 지정합니다(자정 회전, 보관일수 설정).

---

## 5. 패키징 & 배포 (Nexus / PyPI) (요구사항 15)

### 5-1. 빌드 (공통)

```bash
pip install --upgrade build twine
python -m build          # dist/*.whl + dist/*.tar.gz 생성
twine check dist/*       # 메타데이터 검증
```

> `pyproject.toml` 의 `package-data` 설정으로 `pxa/db/sql/*.sql` 이 wheel 에 포함됩니다. 같은 버전은 덮어쓸 수 없으니 업로드마다 `version` 을 올리세요(0.1.0 → 0.1.1).

### 5-2. 사내 Nexus 업로드 (권장)

Nexus 에서 **PyPI(hosted)** 저장소(예: `pypi-internal`)를 만든 뒤 `~/.pypirc` 등록:

```ini
[distutils]
index-servers =
    nexus

[nexus]
repository = https://nexus.example.com/repository/pypi-internal/
username = <NEXUS_USER>
password = <NEXUS_TOKEN>
```

업로드 & 설치:

```bash
twine upload -r nexus dist/*
pip install pxa-framework --index-url https://nexus.example.com/repository/pypi-internal/simple/
```

매번 옵션 주기 번거로우면 `pip.conf`(Windows `pip.ini`)에 고정:

```ini
[global]
index-url = https://nexus.example.com/repository/pypi-internal/simple/
extra-index-url = https://pypi.org/simple/
```

### 5-3. 공개 PyPI 업로드 (외부 공개 시)

API 토큰 인증(`username` 은 리터럴 `__token__`). TestPyPI 리허설 후 운영 업로드를 권장합니다.

```bash
twine upload --repository testpypi dist/*    # (선택) 리허설
twine upload dist/*                          # 운영 PyPI
```

> 공개 PyPI 는 패키지명이 전역 유일해야 합니다. `pxa-framework` 가 점유돼 있으면 `pyproject.toml` 의 `name` 을 고유값(예: `yourorg-pxa-framework`)으로 바꾸세요. 사내 전용이면 5-2(Nexus)만으로 충분합니다.

### 5-4. Windows 빌드 에러 트러블슈팅

다음 에러는 **Windows 260자 경로 제한(MAX_PATH)** 때문입니다(프로젝트가 매우 긴 경로 아래 있을 때):

```
error: could not create '...\src\pxa_framework.egg-info\dependency_links.txt': No such file or directory
ERROR Backend subprocess exited when trying to invoke build_sdist
```

`python -m build` 가 만드는 중첩 임시 경로(`pxa_framework-0.1.0\src\pxa_framework.egg-info\...`)가 260자를 넘으면 Windows 가 파일 생성에 실패하고 ENOENT(No such file or directory)로 보고됩니다. 해결책:

- **(가장 쉬움) 짧은 경로에서 빌드**:
  ```bat
  robocopy "%CD%" C:\build\pxa /E /XD .venv dist build /XF *.egg-info
  cd /d C:\build\pxa
  rmdir /s /q src\pxa_framework.egg-info 2>nul
  py -m build
  ```
- **Windows 긴 경로 허용**: 레지스트리 `HKLM\SYSTEM\CurrentControlSet\Control\FileSystem` 의 `LongPathsEnabled` = `1` 설정 후 재부팅.
- **wheel 만 빌드**: `py -m build --wheel` (Nexus 업로드엔 충분).
- **잔여물 정리**: 프로젝트의 `.venv`, `*.egg-info`, `build`, `dist` 를 지운 뒤 빌드.

### 5-5. (선택) CI 자동 배포

태그 push 시: `python -m build` → `twine check dist/*` → `twine upload`(자격증명은 CI 시크릿). 버전 태그와 `pyproject.toml` 버전을 일치시키세요.

---

## 6. 테스트 & 네이밍 점검 (요구사항 14, 16)

```bash
pip install -e ".[dev]"

pytest                 # API 위주 테스트 (DB 연결 제외, fakeredis 사용)
flake8 src tests       # PEP8 + pep8-naming(N8xx) 규칙 강제 점검
```

CI 에서 `flake8` 종료코드가 0이 아니면 빌드를 실패시켜 네이밍 위반을 차단하세요.

---

## 7. 디렉토리 구조

```
pxa-framework/
├── pyproject.toml          # 패키지/의존성/빌드 설정
├── .flake8                 # PEP 네이밍 강제 (요구사항 16)
├── config/config.yaml      # 환경설정 (요구사항 10, 12)
├── src/pxa/
│   ├── app.py              # create_app 팩토리
│   ├── fastapi.py          # FastAPI 파사드 (fastapi 비노출)
│   ├── config.py           # 설정 로더
│   ├── codes.py            # AppCode (pxa-1xxxx/2xxxx)
│   ├── response.py         # ApiResponse 표준 포맷
│   ├── exceptions.py       # 공통 예외 + 핸들러
│   ├── logging_conf.py     # 로깅
│   ├── middleware.py       # 요청 로깅
│   ├── auth/               # Opaque Token + Redis + 쿠키
│   ├── db/                 # 엔진/풀/트랜잭션/ORM/SQL
│   │   └── sql/            # 분리된 SQL 파일
│   └── models/             # ORM 모델
├── example/main.py         # 주니어 개발자 예제
└── tests/                  # pytest (API 위주)
```
