feat: Flask 애플리케이션 모듈화 및 웹 대시보드 구현
- Flask Blueprint 아키텍처로 전환 (dashboard, upload, backup, status) - app.py 681줄 95줄로 축소 (86% 감소) - HTML 템플릿 모듈화 (base.html + 기능별 templates) - CSS/JS 파일 분리 (common + 기능별 파일) - 대시보드 기능 추가 (통계, 주간 예보, 방문객 추이) - 파일 업로드 웹 인터페이스 구현 - 백업/복구 관리 UI 구현 - Docker 배포 환경 개선 - .gitignore 업데이트 (uploads, backups, cache 등)
This commit is contained in:
420
DEVELOPMENT.md
Normal file
420
DEVELOPMENT.md
Normal file
@ -0,0 +1,420 @@
|
||||
# 개발자 가이드 (Developer Guide)
|
||||
|
||||
이 문서는 First Garden Static Analysis Service에 기여하거나 개발하고자 하는 개발자를 위한 가이드입니다.
|
||||
|
||||
## 개발 환경 설정
|
||||
|
||||
### 1. 저장소 클론
|
||||
```bash
|
||||
git clone https://git.siane.kr/firstgarden/static.git
|
||||
cd static
|
||||
```
|
||||
|
||||
### 2. Python 가상환경 생성
|
||||
```bash
|
||||
# Python 3.11 이상 필수
|
||||
python3.11 -m venv venv
|
||||
|
||||
# 가상환경 활성화
|
||||
# Linux/macOS
|
||||
source venv/bin/activate
|
||||
|
||||
# Windows
|
||||
.\venv\Scripts\activate
|
||||
```
|
||||
|
||||
### 3. 의존성 설치
|
||||
```bash
|
||||
pip install --upgrade pip setuptools wheel
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 개발 도구 추가 설치
|
||||
pip install pytest pytest-cov black flake8 mypy
|
||||
```
|
||||
|
||||
### 4. 환경 변수 설정
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# .env 파일을 편집하여 로컬 DB 정보 입력
|
||||
```
|
||||
|
||||
### 5. 로컬 데이터베이스 설정
|
||||
```bash
|
||||
# Docker로 MariaDB 실행 (기존 DB가 없는 경우)
|
||||
docker run --name fg-static-db \
|
||||
-e MYSQL_ROOT_PASSWORD=rootpassword \
|
||||
-e MYSQL_DATABASE=firstgarden \
|
||||
-e MYSQL_USER=firstgarden \
|
||||
-e MYSQL_PASSWORD=Fg9576861! \
|
||||
-p 3306:3306 \
|
||||
mariadb:11.2-jammy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 코드 스타일
|
||||
|
||||
### PEP 8 준수
|
||||
```bash
|
||||
# 코드 포매팅
|
||||
black lib/ conf/ daily_run.py
|
||||
|
||||
# 린트 검사
|
||||
flake8 lib/ conf/ daily_run.py --max-line-length=100
|
||||
|
||||
# 타입 검사
|
||||
mypy lib/ conf/ --ignore-missing-imports
|
||||
```
|
||||
|
||||
### 명명 규칙
|
||||
- 함수/변수: `snake_case`
|
||||
- 클래스: `PascalCase`
|
||||
- 상수: `UPPER_CASE`
|
||||
- 비공개 함수: `_leading_underscore`
|
||||
|
||||
### 문서화
|
||||
모든 함수에 docstring 작성:
|
||||
```python
|
||||
def fetch_data(start_date, end_date, **kwargs):
|
||||
"""
|
||||
데이터 조회 함수
|
||||
|
||||
Args:
|
||||
start_date (datetime.date): 시작 날짜
|
||||
end_date (datetime.date): 종료 날짜
|
||||
**kwargs: 추가 매개변수
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 조회된 데이터
|
||||
|
||||
Raises:
|
||||
ValueError: 유효하지 않은 날짜 범위
|
||||
DatabaseError: DB 연결 실패
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 로깅 사용법
|
||||
|
||||
```python
|
||||
from lib.common import setup_logging
|
||||
|
||||
# 로거 생성
|
||||
logger = setup_logging('module_name', 'INFO')
|
||||
|
||||
# 로그 출력
|
||||
logger.info('정보 메시지')
|
||||
logger.warning('경고 메시지')
|
||||
logger.error('에러 메시지', exc_info=True) # 스택 트레이스 포함
|
||||
logger.debug('디버그 메시지')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 데이터베이스 작업
|
||||
|
||||
### 세션 관리
|
||||
```python
|
||||
from conf.db import DBSession
|
||||
|
||||
# 권장: 컨텍스트 매니저 사용
|
||||
with DBSession() as session:
|
||||
result = session.execute(select(some_table))
|
||||
# 자동으로 커밋 또는 롤백
|
||||
|
||||
# 또는 기존 방식
|
||||
session = db.get_session()
|
||||
try:
|
||||
result = session.execute(select(some_table))
|
||||
session.commit()
|
||||
finally:
|
||||
session.close()
|
||||
```
|
||||
|
||||
### 쿼리 작성
|
||||
```python
|
||||
from sqlalchemy import select, and_, func
|
||||
from conf import db_schema
|
||||
|
||||
# 데이터 조회
|
||||
session = db.get_session()
|
||||
stmt = select(
|
||||
db_schema.weather.c.date,
|
||||
db_schema.weather.c.maxTa
|
||||
).where(
|
||||
and_(
|
||||
db_schema.weather.c.date >= '2025-01-01',
|
||||
db_schema.weather.c.stnId == 99
|
||||
)
|
||||
)
|
||||
result = session.execute(stmt).fetchall()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 데이터 수집
|
||||
|
||||
### 기본 패턴
|
||||
```python
|
||||
import requests
|
||||
from lib.common import setup_logging, retry_on_exception
|
||||
|
||||
logger = setup_logging(__name__, 'INFO')
|
||||
|
||||
@retry_on_exception(max_retries=3, delay=1.0, backoff=2.0)
|
||||
def fetch_api_data(url, params):
|
||||
"""API 데이터 수집"""
|
||||
try:
|
||||
response = requests.get(url, params=params, timeout=20)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
logger.info(f"API 데이터 수집 완료: {len(data)} 건")
|
||||
return data
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"API 요청 실패: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 테스트 작성
|
||||
|
||||
### 단위 테스트
|
||||
```bash
|
||||
# 테스트 디렉토리 생성
|
||||
mkdir tests
|
||||
|
||||
# 테스트 파일 작성
|
||||
cat > tests/test_common.py << 'EOF'
|
||||
import pytest
|
||||
from lib.common import load_config
|
||||
|
||||
def test_load_config():
|
||||
config = load_config()
|
||||
assert config is not None
|
||||
assert 'database' in config
|
||||
assert 'DATA_API' in config
|
||||
|
||||
def test_load_config_invalid_path():
|
||||
with pytest.raises(FileNotFoundError):
|
||||
load_config('/invalid/path.yaml')
|
||||
EOF
|
||||
|
||||
# 테스트 실행
|
||||
pytest tests/ -v
|
||||
pytest tests/ --cov=lib --cov=conf
|
||||
```
|
||||
|
||||
### 통합 테스트
|
||||
```python
|
||||
# tests/test_integration.py
|
||||
import pytest
|
||||
from conf.db import DBSession
|
||||
from lib.weekly_visitor_forecast_prophet import load_data
|
||||
|
||||
def test_load_data_integration():
|
||||
"""DB에서 데이터 로드 테스트"""
|
||||
with DBSession() as session:
|
||||
from datetime import date, timedelta
|
||||
start_date = date.today() - timedelta(days=30)
|
||||
end_date = date.today()
|
||||
|
||||
df = load_data(session, start_date, end_date)
|
||||
assert len(df) > 0
|
||||
assert 'pos_qty' in df.columns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 모듈 개발 체크리스트
|
||||
|
||||
새로운 모듈을 추가할 때 다음을 확인하세요:
|
||||
|
||||
- [ ] 모든 함수에 docstring 작성
|
||||
- [ ] PEP 8 코드 스타일 준수
|
||||
- [ ] 로깅 추가 (info, warning, error)
|
||||
- [ ] 에러 처리 구현
|
||||
- [ ] 단위 테스트 작성
|
||||
- [ ] `requirements.txt` 업데이트
|
||||
- [ ] README.md 업데이트
|
||||
- [ ] CHANGELOG.md 업데이트
|
||||
|
||||
---
|
||||
|
||||
## Git 워크플로우
|
||||
|
||||
### 기본 브랜치
|
||||
- `main`: 배포 준비 브랜치
|
||||
- `develop`: 개발 메인 브랜치
|
||||
- `feature/*`: 기능 개발
|
||||
- `bugfix/*`: 버그 수정
|
||||
|
||||
### 커밋 메시지 포맷
|
||||
```
|
||||
[타입] 간단한 설명
|
||||
|
||||
더 자세한 설명 (선택사항)
|
||||
|
||||
연관 이슈: #123
|
||||
```
|
||||
|
||||
**타입:**
|
||||
- `feat`: 새로운 기능
|
||||
- `fix`: 버그 수정
|
||||
- `docs`: 문서화
|
||||
- `refactor`: 코드 리팩토링
|
||||
- `test`: 테스트 추가
|
||||
- `chore`: 설정 변경
|
||||
|
||||
### 브랜치 생성 및 병합
|
||||
```bash
|
||||
# 브랜치 생성
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b feature/새로운기능
|
||||
|
||||
# 커밋
|
||||
git add .
|
||||
git commit -m "[feat] 새로운 기능 추가"
|
||||
|
||||
# 푸시
|
||||
git push origin feature/새로운기능
|
||||
|
||||
# Merge Request/Pull Request 생성 후 코드 리뷰
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD 파이프라인
|
||||
|
||||
### GitHub Actions (예상 설정)
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: pip install -r requirements.txt
|
||||
- run: pytest tests/ --cov=lib --cov=conf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 문제 해결
|
||||
|
||||
### 일반적인 개발 문제
|
||||
|
||||
**Q: DB 연결 실패**
|
||||
```bash
|
||||
# DB 상태 확인
|
||||
docker ps | grep mariadb
|
||||
|
||||
# DB 접속 확인
|
||||
mysql -h localhost -u firstgarden -p firstgarden
|
||||
|
||||
# conf/config.yaml에서 DB 정보 확인
|
||||
```
|
||||
|
||||
**Q: 패키지 설치 오류**
|
||||
```bash
|
||||
# 캐시 초기화
|
||||
pip cache purge
|
||||
|
||||
# 의존성 재설치
|
||||
pip install -r requirements.txt --force-reinstall
|
||||
```
|
||||
|
||||
**Q: 포트 이미 사용 중**
|
||||
```bash
|
||||
# 기존 컨테이너 제거
|
||||
docker-compose down -v
|
||||
|
||||
# 포트 사용 프로세스 확인
|
||||
lsof -i :3306
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 성능 프로파일링
|
||||
|
||||
### 실행 시간 측정
|
||||
```python
|
||||
import time
|
||||
from lib.common import setup_logging
|
||||
|
||||
logger = setup_logging(__name__)
|
||||
|
||||
start = time.time()
|
||||
# 코드 실행
|
||||
elapsed = time.time() - start
|
||||
logger.info(f"실행 시간: {elapsed:.2f}초")
|
||||
```
|
||||
|
||||
### 메모리 프로파일링
|
||||
```bash
|
||||
# memory_profiler 설치
|
||||
pip install memory-profiler
|
||||
|
||||
# 스크립트 실행
|
||||
python -m memory_profiler script.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 배포 준비
|
||||
|
||||
### 프로덕션 체크리스트
|
||||
- [ ] 모든 테스트 통과
|
||||
- [ ] 코드 리뷰 완료
|
||||
- [ ] 버전 번호 업데이트 (CHANGELOG.md)
|
||||
- [ ] 환경 변수 검증
|
||||
- [ ] 데이터베이스 마이그레이션 확인
|
||||
- [ ] Docker 이미지 빌드 및 테스트
|
||||
- [ ] 보안 취약점 검사
|
||||
- [ ] 성능 벤치마크
|
||||
|
||||
---
|
||||
|
||||
## 유용한 명령어
|
||||
|
||||
```bash
|
||||
# 개발 모드로 실행
|
||||
PYTHONUNBUFFERED=1 python daily_run.py
|
||||
|
||||
# 로그 모니터링
|
||||
tail -f logs/daily_run.log
|
||||
|
||||
# Docker 컨테이너 로그
|
||||
docker-compose logs -f fg-static
|
||||
|
||||
# 데이터베이스 접속
|
||||
docker-compose exec mariadb mysql -u firstgarden -p firstgarden
|
||||
|
||||
# 파이썬 인터랙티브 셸
|
||||
python -c "import sys; sys.path.insert(0, '.'); from conf import db; print(db.load_config())"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 참고 자료
|
||||
|
||||
- [Python 공식 가이드](https://docs.python.org/)
|
||||
- [SQLAlchemy 문서](https://docs.sqlalchemy.org/)
|
||||
- [Prophet 가이드](https://facebook.github.io/prophet/docs/installation.html)
|
||||
- [Docker 문서](https://docs.docker.com/)
|
||||
- [Git 가이드](https://git-scm.com/doc)
|
||||
|
||||
---
|
||||
|
||||
**작성일**: 2025-12-26
|
||||
**마지막 업데이트**: 2025-12-26
|
||||
Reference in New Issue
Block a user