🔍 파이썬 레포지토리 자동 분석 스크립트: 실행 예시 + 상세 해설 + 전체 코드

키워드: 파이썬 코드 분석, 레포지토리 헬스체크, LOC, 의존성 분석, 복잡도, 유지보수성, pytest, AST, 그래프 시각화

파이썬 프로젝트의 상태를 한 번에 파악하고 싶을 때가 많죠. 이 글은 그런 순간을 위해 준비한 **자동 분석 스크립트(analyze_repo.sh)**의 실행 예시, 상세 동작 해설, 그리고 전체 코드까지 한 번에 제공합니다. 스크립트는 pygount/radon/pydeps/pipreqs/pytest/ripgrep 등 도구를 조합해 레포지토리 헬스 리포트를 생성합니다.


🚀 한눈에 보는 기능 요약

  • 디렉터리 트리 & 파일 크기: tree / find, du
  • LOC(코드 라인) 통계: pygount (언어별/주석/빈 줄)
  • 의존성 분석: pipreqs(추정), pipdeptree(실제 설치 트리)
  • 복잡도/유지보수성: radon cc, radon mi
  • 임포트 그래프: pydeps  Graphviz SVG
  • 테스트 수집: pytest --collect-only
  • TODO/FIXME 스캔: ripgrep(또는 grep)
  • AST 인덱스: 함수/클래스를 JSON으로 색인
  • Fallback 의존성 수집기: 문법 오류 파일도 관대하게 import 추출

⚙️ 설치 (1회)

python -m venv .venv && source .venv/bin/activate
pip install --upgrade pip
pip install pipreqs pipdeptree radon pydeps pygount pytest

# 의존(권장)
# - graphviz: pydeps가 SVG/DOT 출력에 필요
# - tree: 디렉터리 맵 생성
# - ripgrep: TODO/FIXME 검색 가속(없으면 grep로 대체)
# Fedora/RHEL/CentOS:
#   sudo dnf install graphviz tree ripgrep -y
# Ubuntu/Debian:
#   sudo apt-get update && sudo apt-get install graphviz tree ripgrep -y

▶️ 사용 방법

chmod +x analyze_repo.sh
./analyze_repo.sh

업로드 권장 세트(요약 리포트)

  • analysis_out/repo_map.txt, file_sizes.txt, loc_summary.txt
  • pipdeptree.txt, complexity.json, maintainability.json
  • imports.svg(생성 시), test_collection.txt, todos.txt, ast_index.json
  • requirements_inferred.txt 또는 requirements_inferred_fallback.txt

🧩 실행 예시 & 결과 해설 (요점)

  1. 레포 트리/파일 크기 → 구조 파악 & 용량 상위 파일 확인
  2. LOC 통계 → 코드/주석/빈 줄 밸런스 진단
  3. 의존성 → “실제 설치 트리” vs “코드가 요구하는 패키지” 비교
  4. 복잡도/유지보수성 → 리팩터링 우선순위 선정 근거
  5. 임포트 그래프 → 결합도 높은 모듈 식별
  6. 테스트 수집 → 테스트 존재/명명 규칙 파악
  7. TODO/FIXME → 작업 백로그 추출
  8. AST 인덱스 → 함수/클래스 탐색 및 문서화 자동화 토대

💡 실전 팁

  • IGNORE_PIPREQS에 문법 오류/실험용 스크립트를 추가하면 pipreqs 실패를 줄일 수 있습니다.
  • --max-bacon=2로 import 그래프 깊이 제한 → 복잡한 그래프 가독성 개선.
  • CI 파이프라인에 이 스크립트를 연결해 정기 리포트를 만들면 팀 생산성이 올라갑니다.

📜 analyze_repo.sh 전체 코드

아래 코드를 레포지토리 루트에 analyze_repo.sh로 저장하세요.

#!/usr/bin/env bash
set -euo pipefail

# === 설정 ===
OUT="analysis_out"
IGNORE_PIPREQS="main_bat.py"    # pipreqs가 깨지는 파일/폴더를 ,로 추가(예: "main_bat.py,build,dist")
SKIP_DIRS=".git,.venv,__pycache__,.mypy_cache,.pytest_cache,${OUT}"
PY_EXT=".py"

mkdir -p "$OUT"

# === 유틸 ===
have() { command -v "$1" >/dev/null 2>&1; }

note() { echo -e "\n[INFO] $*"; }
warn() { echo -e "\n[WARN] $*" >&2; }
fail() { echo -e "\n[ERROR] $*" >&2; exit 1; }

# === 1) 레포 트리 & 파일 크기 ===
note "레포 맵/파일 크기 수집..."
{
  echo "### REPO TREE"
  if have tree; then
    tree -a -I "$(echo "$SKIP_DIRS" | tr , '|')" || true
  else
    warn "'tree'가 없어 'find'로 대체합니다(간략 출력). 'sudo dnf/apt install tree' 권장."
    find . -path "./.git" -prune -o -path "./${OUT}" -prune -o -print
  fi
} > "$OUT/repo_map.txt"

du -ah --max-depth=2 2>/dev/null | sort -h > "$OUT/file_sizes.txt" || {
  warn "du 옵션 미지원 환경. 대체 모드로 파일 크기 집계."
  find . -type f -not -path "./${OUT}/*" -not -path "./.git/*" -exec du -ah {} + | sort -h > "$OUT/file_sizes.txt" || true
}

# === 2) LOC/언어 통계 (pygount) ===
note "LOC/언어 통계(pygount)..."
pygount . \
  --format=summary \
  --folders-to-skip "$SKIP_DIRS" \
  > "$OUT/loc_summary.txt"

# (선택) 파일별 상세 필요 시 주석 해제
# pygount . \
#   --format=cloc-xml \
#   --folders-to-skip "$SKIP_DIRS" \
#   > "$OUT/loc_by_file.cloc.xml"

# === 3) 의존성(추정 + 실제 트리) ===
note "의존성 추정(pipreqs) 및 설치 트리(pipdeptree)..."
# pipreqs는 문법 에러 파일에서 중단될 수 있어 --ignore 사용, 실패해도 계속
pipreqs . \
  --force \
  --encoding=utf-8 \
  --ignore "$IGNORE_PIPREQS" \
  --savepath "$OUT/requirements_inferred.txt" || warn "pipreqs 실패(무시하고 계속). --ignore 목록을 늘리거나 fallback 사용 권장."

# requirements.txt가 있다면 설치 후 의존 트리 출력(실패 허용)
if [[ -f requirements.txt ]]; then
  pip install -r requirements.txt || warn "requirements 설치 중 경고 발생(계속 진행)."
fi
pipdeptree > "$OUT/pipdeptree.txt" || warn "pipdeptree 실패(계속)."

# === 4) 복잡도/유지보수성 (radon) ===
note "복잡도/유지보수성(radon)..."
radon cc -s -a -j . > "$OUT/complexity.json"    || warn "radon cc 실패(계속)."
radon mi -s -j . > "$OUT/maintainability.json"  || warn "radon mi 실패(계속)."

# === 5) import 그래프 (pydeps) ===
note "import 그래프(pydeps → SVG)..."
if have dot; then
  :
else
  warn "graphviz(dot) 미설치. 'sudo dnf/apt install graphviz' 권장. 시도는 해봅니다."
fi
# 최신 pydeps: -T svg -o 파일
if have pydeps; then
  pydeps . --noshow --max-bacon=2 -T svg -o "$OUT/imports.svg" || warn "pydeps 그래프 생성 실패(계속)."
else
  warn "pydeps 미설치. 'pip install pydeps' 후 재시도 가능."
fi

# === 6) 테스트 수집 (pytest) ===
note "pytest 테스트 수집..."
pytest --collect-only -q > "$OUT/test_collection.txt" || warn "pytest 수집 실패(계속)."

# === 7) TODO/FIXME/HACK/XXX 검색 ===
note "TODO/FIXME/HACK/XXX 키워드 검색..."
if have rg; then
  rg -n "TODO|FIXME|HACK|XXX" --glob "!${OUT}/**" --glob "!.git/**" > "$OUT/todos.txt" || true
else
  grep -RInE "TODO|FIXME|HACK|XXX" . \
    --exclude-dir="$OUT" --exclude-dir=".git" --exclude-dir=".venv" \
    > "$OUT/todos.txt" || true
fi

# === 8) AST 인덱스(함수/클래스) ===
note "AST 인덱스 생성(함수/클래스)..."
python - << 'PY' || warn "AST 인덱스 생성 중 문제(계속)."
import os, ast, json
skip = set(s.strip() for s in ".git,.venv,__pycache__,.mypy_cache,.pytest_cache,analysis_out".split(","))
index=[]
for root,_,files in os.walk("."):
    if any(k in root for k in skip): 
        continue
    for f in files:
        if f.endswith(".py"):
            p=os.path.join(root,f)
            try:
                with open(p,"r",encoding="utf-8",errors="ignore") as fh:
                    src = fh.read()
                try:
                    m=ast.parse(src, filename=p)
                except SyntaxError as e:
                    # 문법 에러는 기록만 남기고 건너뜀
                    index.append({"path":p,"error":f"SyntaxError: {e}"})
                    continue
                funcs=[n.name for n in ast.walk(m) if isinstance(n,ast.FunctionDef)]
                clss=[n.name for n in ast.walk(m) if isinstance(n,ast.ClassDef)]
                index.append({"path":p,"functions":funcs,"classes":clss})
            except Exception as e:
                index.append({"path":p,"error":str(e)})
open("analysis_out/ast_index.json","w",encoding="utf-8").write(json.dumps(index,ensure_ascii=False,indent=2))
PY

# === 9) (옵션) pipreqs Fallback: 관대한 import 수집기 ===
note "pipreqs Fallback(관대한 import 수집기) 실행..."
python - << 'PY' || warn "Fallback import 수집 실패(계속)."
import os, ast, tokenize, io
root="."
skip=set(s.strip() for s in ".git,.venv,__pycache__,.mypy_cache,.pytest_cache,analysis_out".split(","))
imports=set()
stdlib={"os","sys","json","re","subprocess","pathlib","typing","logging","datetime","time","math","itertools","functools","collections","dataclasses","ast","tokenize","io","enum","statistics","decimal","random","uuid"}

def tolerant_parse(text):
    try:
        return ast.parse(text)
    except SyntaxError:
        return None

for r,_,fs in os.walk(root):
    if any(k in r for k in skip): 
        continue
    for f in fs:
        if f.endswith(".py"):
            p=os.path.join(r,f)
            try:
                txt=open(p,"r",encoding="utf-8",errors="ignore").read()
            except Exception:
                continue
            m=tolerant_parse(txt)
            if m:
                for n in ast.walk(m):
                    if isinstance(n, ast.Import):
                        for a in n.names:
                            imports.add(a.name.split(".")[0])
                    elif isinstance(n, ast.ImportFrom) and n.module:
                        imports.add(n.module.split(".")[0])
            else:
                # 간단 라인 스캔(문법 에러 파일용)
                for line in txt.splitlines():
                    line=line.strip()
                    if line.startswith("import "):
                        for part in line[len("import "):].split(","):
                            imports.add(part.strip().split(".")[0])
                    elif line.startswith("from ") and " import " in line:
                        mod=line.split()[1]
                        imports.add(mod.split(".")[0])

third=sorted(m for m in imports if m and m not in stdlib)
open("analysis_out/requirements_inferred_fallback.txt","w",encoding="utf-8").write("\n".join(third))
print(f"[fallback] third-party candidates: {len(third)} → analysis_out/requirements_inferred_fallback.txt")
PY

# === 완료 요약 ===
note "완료! 산출물 목록:"
ls -lh "$OUT" || true
echo
echo "업로드 권장 파일:"
echo "- $OUT/repo_map.txt"
echo "- $OUT/file_sizes.txt"
echo "- $OUT/loc_summary.txt"
echo "- $OUT/pipdeptree.txt"
echo "- $OUT/complexity.json"
echo "- $OUT/maintainability.json"
echo "- $OUT/imports.svg (있으면)"
echo "- $OUT/test_collection.txt"
echo "- $OUT/todos.txt"
echo "- $OUT/ast_index.json"
echo "- $OUT/requirements_inferred.txt (있으면)"
echo "- $OUT/requirements_inferred_fallback.txt (보조)"

 


✅ 요약 & 결론

  • 이 스크립트 하나로 프로젝트 상태 진단을 자동화할 수 있습니다.
  • CI에 붙이면 정기 건강 리포트를 자동 생성할 수 있고, 신규 인수/레거시 관리/리팩터링 기획에 탁월합니다.
  • 필요시 파일별 LOC CSV 호출 그래프 생성기 등 확장도 손쉽게 추가 가능합니다.

본 글은 chatgpt를 활용하여 작성되었습니다.

'etc' 카테고리의 다른 글

ssh key로 서버 접속  (0) 2020.05.18
bash Prompt 설정  (0) 2020.05.18
ubuntu user 생성  (0) 2020.04.20
git command  (0) 2020.03.16
ansible로 ubuntu user 생성  (0) 2020.02.19
Posted by jerymy
,