Post

파이썬 API 문서 자동 생성하기

자신만의 모듈을 만들고 나면 함수나 클래스 단위로 주석을 작성합니다. 이 주석을 읽어들여서 레퍼런스 문서를 자동으로 생성해 주는 기능을 소개합니다.

파이썬 API 문서 자동 생성하기

#01. 모듈화 기능의 주석문 예시

파이썬에서 함수 단위의 주석은 아래와 같은 형식으로 작성됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def my_normalize_data(
    mean: float, std: float, size: int = 100, round: int = 2
) -> np.ndarray:
    """정규분포를 따르는 데이터를 생성한다.

    Args:
        mean (float): 평균
        std (float): 표준편차
        size (int, optional): 데이터 크기. Defaults to 1000.

    Returns:
        np.ndarray: 정규분포를 따르는 데이터
    """
    p = 0
    x = []
    while p < 0.05:
        x = np.random.normal(mean, std, size).round(round)
        _, p = normaltest(x)

    return x

#02. 레퍼런스 생성 자동화 하기

[1] 패키지 설치

pdoc 이라는 패키지를 설치합니다.

1
pip install pdoc

[2] 문서 생성

작업 폴더 위치에서 명령 수행

1
pdoc --html --output-dir 생성될폴더이름 패키지폴더이름

사용 예

1
pdoc --html --output-dir docs helper

[3] 고급 옵션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 브라우저에서 자동으로 열기
pdoc --html --output-dir docs --browse helper

# 서버 모드로 실행 (실시간 업데이트)
pdoc --http localhost:8080 helper

# 외부 링크 포함하여 생성
pdoc --html --output-dir docs --external-links helper

# 특정 모듈만 문서화
pdoc --html --output-dir docs helper.utils

# 프라이빗 멤버 포함
pdoc --html --output-dir docs --show-source helper

#03. 다양한 문서화 도구 비교

1) pdoc vs 다른 도구들

도구장점단점사용 상황
pdoc간단한 설정, 빠른 생성커스터마이징 제한적개인 프로젝트, 빠른 문서화
Sphinx강력한 기능, 확장성복잡한 설정, 학습 곡선대규모 프로젝트, 공식 문서
MkDocsMarkdown 지원, 테마 풍부Python 특화 기능 부족일반적인 문서 사이트
pydoc내장 도구, 추가 설치 불필요기본적인 기능만 제공간단한 확인용

2) Sphinx 사용하기

설치 및 설정

1
pip install sphinx sphinx-rtd-theme

프로젝트 초기화

1
2
3
4
5
6
# 문서 디렉토리 생성
mkdir docs
cd docs

# Sphinx 프로젝트 초기화
sphinx-quickstart

conf.py 설정 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Configuration file for the Sphinx documentation builder.

import os
import sys
sys.path.insert(0, os.path.abspath('..'))

project = 'My Python Project'
author = 'Your Name'
release = '1.0.0'

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.viewcode',
    'sphinx.ext.napoleon',
    'sphinx.ext.intersphinx',
]

html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']

# Napoleon settings (Google/NumPy 스타일 docstring 지원)
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = False
napoleon_include_private_with_doc = False

자동 문서 생성

1
2
3
4
5
# API 문서 자동 생성
sphinx-apidoc -o . ../myproject

# HTML 빌드
make html

#04. 고급 주석 작성 가이드

1) Google 스타일 Docstring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def calculate_statistics(data, method='mean', axis=None):
    """Calculate statistics for the given data.

    This function calculates various statistical measures for the input data
    using different methods.

    Args:
        data (array_like): Input data array.
        method (str, optional): Statistical method to use.
            Options are 'mean', 'median', 'std'. Defaults to 'mean'.
        axis (int, optional): Axis along which to calculate statistics.
            If None, calculate for flattened array. Defaults to None.

    Returns:
        float or ndarray: Calculated statistic value(s).

    Raises:
        ValueError: If method is not supported.
        TypeError: If data is not array_like.

    Example:
        >>> import numpy as np
        >>> data = np.array([1, 2, 3, 4, 5])
        >>> calculate_statistics(data, method='mean')
        3.0
        >>> calculate_statistics(data, method='std')
        1.4142135623730951

    Note:
        This function uses NumPy internally for calculations.
    """
    import numpy as np

    if not hasattr(data, '__iter__'):
        raise TypeError("Data must be array_like")

    data = np.asarray(data)

    if method == 'mean':
        return np.mean(data, axis=axis)
    elif method == 'median':
        return np.median(data, axis=axis)
    elif method == 'std':
        return np.std(data, axis=axis)
    else:
        raise ValueError(f"Unsupported method: {method}")

2) NumPy 스타일 Docstring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def process_matrix(matrix, operation='transpose', inplace=False):
    """
    Process a matrix with specified operation.

    Parameters
    ----------
    matrix : array_like
        Input matrix to process.
    operation : {'transpose', 'inverse', 'determinant'}, optional
        Operation to perform on the matrix.
        Default is 'transpose'.
    inplace : bool, optional
        Whether to modify the matrix in-place.
        Default is False.

    Returns
    -------
    ndarray or float
        Processed matrix or scalar value depending on operation.

    Raises
    ------
    ValueError
        If operation is not supported.
    LinAlgError
        If matrix operation fails (e.g., singular matrix for inverse).

    See Also
    --------
    numpy.transpose : Transpose operation
    numpy.linalg.inv : Matrix inverse
    numpy.linalg.det : Matrix determinant

    Examples
    --------
    >>> import numpy as np
    >>> matrix = np.array([[1, 2], [3, 4]])
    >>> process_matrix(matrix, 'transpose')
    array([[1, 3],
           [2, 4]])
    >>> process_matrix(matrix, 'determinant')
    -2.0
    """
    import numpy as np

    matrix = np.asarray(matrix)

    if operation == 'transpose':
        return matrix.T if not inplace else matrix.T
    elif operation == 'inverse':
        return np.linalg.inv(matrix)
    elif operation == 'determinant':
        return np.linalg.det(matrix)
    else:
        raise ValueError(f"Unsupported operation: {operation}")

3) 클래스 문서화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class DataProcessor:
    """A class for processing various types of data.

    This class provides methods for cleaning, transforming, and analyzing
    different types of data including numerical and text data.

    Attributes:
        data (pandas.DataFrame): The loaded data.
        config (dict): Configuration parameters for processing.

    Example:
        >>> processor = DataProcessor()
        >>> processor.load_data('data.csv')
        >>> cleaned_data = processor.clean_data()
    """

    def __init__(self, config=None):
        """Initialize the DataProcessor.

        Args:
            config (dict, optional): Configuration dictionary.
                If None, uses default configuration.
        """
        self.data = None
        self.config = config or self._default_config()

    def _default_config(self):
        """Return default configuration.

        Returns:
            dict: Default configuration parameters.
        """
        return {
            'remove_duplicates': True,
            'handle_missing': 'drop',
            'normalize': False
        }

    def load_data(self, filepath, **kwargs):
        """Load data from file.

        Args:
            filepath (str): Path to the data file.
            **kwargs: Additional arguments passed to pandas.read_csv.

        Returns:
            pandas.DataFrame: Loaded data.

        Raises:
            FileNotFoundError: If file doesn't exist.
            ValueError: If file format is not supported.
        """
        import pandas as pd
        import os

        if not os.path.exists(filepath):
            raise FileNotFoundError(f"File not found: {filepath}")

        try:
            self.data = pd.read_csv(filepath, **kwargs)
            return self.data
        except Exception as e:
            raise ValueError(f"Failed to load data: {e}")

#05. 문서화 자동화 스크립트

1) 배치 문서 생성 스크립트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/env python3
"""
Automated documentation generation script.
"""

import os
import subprocess
import argparse
from pathlib import Path

def generate_pdoc_docs(source_dir, output_dir, open_browser=False):
    """Generate documentation using pdoc.

    Args:
        source_dir (str): Source code directory.
        output_dir (str): Output documentation directory.
        open_browser (bool): Whether to open browser after generation.
    """
    cmd = [
        'pdoc',
        '--html',
        '--output-dir', output_dir,
        source_dir
    ]

    if open_browser:
        cmd.append('--browse')

    try:
        subprocess.run(cmd, check=True)
        print(f"Documentation generated successfully in {output_dir}")
    except subprocess.CalledProcessError as e:
        print(f"Error generating documentation: {e}")

def generate_sphinx_docs(source_dir, docs_dir):
    """Generate documentation using Sphinx.

    Args:
        source_dir (str): Source code directory.
        docs_dir (str): Documentation directory.
    """
    # API 문서 자동 생성
    subprocess.run([
        'sphinx-apidoc',
        '-o', docs_dir,
        source_dir,
        '--force'
    ], check=True)

    # HTML 빌드
    os.chdir(docs_dir)
    subprocess.run(['make', 'html'], check=True)
    print(f"Sphinx documentation built in {docs_dir}/_build/html")

def main():
    parser = argparse.ArgumentParser(description='Generate Python API documentation')
    parser.add_argument('source', help='Source code directory')
    parser.add_argument('-o', '--output', default='docs', help='Output directory')
    parser.add_argument('--tool', choices=['pdoc', 'sphinx'], default='pdoc',
                       help='Documentation tool to use')
    parser.add_argument('--browse', action='store_true',
                       help='Open browser after generation (pdoc only)')

    args = parser.parse_args()

    if args.tool == 'pdoc':
        generate_pdoc_docs(args.source, args.output, args.browse)
    elif args.tool == 'sphinx':
        generate_sphinx_docs(args.source, args.output)

if __name__ == '__main__':
    main()

2) GitHub Actions를 이용한 자동 배포

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# .github/workflows/docs.yml
name: Generate and Deploy Documentation

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  docs:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pdoc3 sphinx sphinx-rtd-theme
        pip install -r requirements.txt

    - name: Generate documentation
      run: |
        pdoc --html --output-dir docs mypackage

    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      if: github.ref == 'refs/heads/main'
      with:
        github_token: $
        publish_dir: ./docs

#06. 문서화 모범 사례

1) 좋은 Docstring 작성 원칙

✅ 해야 할 것

  • 함수의 목적을 명확히 설명
  • 모든 매개변수와 반환값 문서화
  • 예외 상황 명시
  • 사용 예시 제공
  • 일관된 스타일 사용

❌ 하지 말아야 할 것

  • 코드를 단순히 반복하는 설명
  • 구현 세부사항에만 집중
  • 매개변수 타입 정보 누락
  • 모호하거나 불완전한 설명

2) 프로젝트 문서 구조

1
2
3
4
5
6
7
8
9
10
11
12
project/
├── README.md                 # 프로젝트 개요
├── docs/                     # 문서 디렉토리
│   ├── api/                  # API 레퍼런스
│   ├── tutorials/            # 튜토리얼
│   ├── examples/             # 예제 코드
│   └── changelog.md          # 변경 로그
├── mypackage/               # 소스 코드
│   ├── __init__.py
│   ├── core.py
│   └── utils.py
└── tests/                   # 테스트 코드

3) 지속적인 문서 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# make_docs.py - 문서 생성 스크립트
import subprocess
import sys
from pathlib import Path

def check_docstring_coverage():
    """Check docstring coverage for the project."""
    try:
        result = subprocess.run(['interrogate', '.'],
                              capture_output=True, text=True)
        print("Docstring Coverage Report:")
        print(result.stdout)
    except FileNotFoundError:
        print("Install interrogate: pip install interrogate")

def lint_docstrings():
    """Lint docstrings using pydocstyle."""
    try:
        result = subprocess.run(['pydocstyle', '.'],
                              capture_output=True, text=True)
        if result.stdout:
            print("Docstring Style Issues:")
            print(result.stdout)
        else:
            print("All docstrings follow style guidelines!")
    except FileNotFoundError:
        print("Install pydocstyle: pip install pydocstyle")

if __name__ == '__main__':
    check_docstring_coverage()
    lint_docstrings()

이제 Python API 문서 생성에 대한 포괄적인 가이드가 완성되었습니다. 기본적인 pdoc 사용법부터 고급 Sphinx 설정, 자동화 스크립트, CI/CD 연동까지 실무에서 활용할 수 있는 모든 내용을 포함했습니다.

This post is licensed under CC BY 4.0 by the author.