์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ Python ๋ฌธ์„œํ™”์™€ Sphinx๋กœ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ

๐Ÿ Python ๋ฌธ์„œํ™”์™€ Sphinx๋กœ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ

๊ฐœ๋ฐœ์ž์˜ ์˜์›ํ•œ ์ˆ™์ œ, ๋ฌธ์„œํ™”๋ฅผ ์ž๋™์œผ๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์ด์•ผ๊ธฐ

๐Ÿ“š ์™œ ๋ฌธ์„œํ™”๋Š” ํ•ญ์ƒ ๋ฏธ๋ฃจ๊ฒŒ ๋ ๊นŒ?

์†”์งํžˆ ๋งํ•ด๋ณด์ž. ์ฝ”๋“œ ์งœ๋Š” ๊ฑด ์žฌ๋ฐŒ๋Š”๋ฐ ๋ฌธ์„œ ์“ฐ๋Š” ๊ฑด ์ •๋ง... ์ง€๋ฃจํ•˜์ง€ ์•Š๋‚˜? ๐Ÿ˜…
๋‚˜๋„ ๊ทธ๋žฌ๋‹ค. ๋ฉ‹์ง„ API๋ฅผ ๋งŒ๋“ค์–ด๋†“๊ณ  ๋‚˜์ค‘์— "์ด๊ฑฐ ์–ด๋–ป๊ฒŒ ์“ฐ๋Š” ๊ฑฐ์˜€์ง€?"๋ผ๋ฉฐ ๋‚ด ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ๋œฏ์–ด๋ณด๋Š” ๊ฒฝํ—˜, ํ•œ ๋ฒˆ์ฏค์€ ์žˆ์„ ๊ฑฐ๋‹ค.

๋” ์›ƒ๊ธด ๊ฑด ํŒ€์›์ด "์ด ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ญ์˜ˆ์š”?"๋ผ๊ณ  ๋ฌผ์–ด๋ณผ ๋•Œ๋‹ค. ๋ถ„๋ช… 3๊ฐœ์›” ์ „์— ๋‚ด๊ฐ€ ๋งŒ๋“  ๊ฑด๋ฐ ๊ธฐ์–ต์ด ์•ˆ ๋‚˜์„œ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ฝ์–ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ. ๐Ÿคฆโ€โ™‚๏ธ

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ ์ž๋™ ๋ฌธ์„œํ™” ๋„๊ตฌ๋‹ค. ๊ทธ์ค‘์—์„œ๋„ Python ์ƒํƒœ๊ณ„์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” Sphinx๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋‹ค.

๋ฌธ์„œํ™” ์ž๋™ํ™” ํ”„๋กœ์„ธ์Šค Python ์ฝ”๋“œ Docstring ์ž‘์„ฑ Sphinx ์ž๋™ ํŒŒ์‹ฑ HTML ๋ฌธ์„œ ์™„์„ฑ! ์ž‘์„ฑ ์‹œ๊ฐ„ 10๋ถ„ ๋นŒ๋“œ ์‹œ๊ฐ„ 30์ดˆ ์œ ์ง€๋ณด์ˆ˜ ์ž๋™

๐ŸŽฏ Sphinx๊ฐ€ ๋ญ๊ธธ๋ž˜?

Sphinx๋Š” ์›๋ž˜ Python ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœ๋œ ๋„๊ตฌ๋‹ค. ๐Ÿ“–
์ง€๊ธˆ์€ Python๋ฟ๋งŒ ์•„๋‹ˆ๋ผ C, C++, JavaScript ๋“ฑ ๋‹ค์–‘ํ•œ ์–ธ์–ด์˜ ๋ฌธ์„œํ™”์—๋„ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค.

Sphinx์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์€ ์ด๋ ‡๋‹ค:

โœจ Sphinx์˜ ์ฃผ์š” ํŠน์ง• 1๏ธโƒฃ reStructuredText ๊ธฐ๋ฐ˜: ๋งˆํฌ๋‹ค์šด๋ณด๋‹ค ๊ฐ•๋ ฅํ•œ ๋ฌธ์„œ ์ž‘์„ฑ ํฌ๋งท
2๏ธโƒฃ ์ž๋™ API ๋ฌธ์„œ ์ƒ์„ฑ: ์ฝ”๋“œ์—์„œ ์ง์ ‘ ๋ฌธ์„œ๋ฅผ ์ถ”์ถœ
3๏ธโƒฃ ๋‹ค์–‘ํ•œ ์ถœ๋ ฅ ํ˜•์‹: HTML, PDF, ePub ๋“ฑ ์—ฌ๋Ÿฌ ํฌ๋งท ์ง€์›
4๏ธโƒฃ ํ™•์žฅ์„ฑ: ์ˆ˜๋งŽ์€ ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ…Œ๋งˆ๋กœ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ
5๏ธโƒฃ ํฌ๋กœ์Šค ๋ ˆํผ๋Ÿฐ์Šค: ๋ฌธ์„œ ๋‚ด ๋‹ค๋ฅธ ๋ถ€๋ถ„์„ ์‰ฝ๊ฒŒ ์ฐธ์กฐ ๊ฐ€๋Šฅ

ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•  ๋•Œ, ์ž˜ ์ •๋ฆฌ๋œ API ๋ฌธ์„œ๋Š” ์‹ ๋ขฐ๋„๋ฅผ ํฌ๊ฒŒ ๋†’์—ฌ์ค€๋‹ค. ๐Ÿ’ผ
"์ด ์‚ฌ๋žŒ์€ ์ „๋ฌธ๊ฐ€๊ตฌ๋‚˜"๋ผ๋Š” ์ธ์ƒ์„ ์ฃผ๋Š” ๊ฐ€์žฅ ํ™•์‹คํ•œ ๋ฐฉ๋ฒ•์ด๊ธฐ๋„ ํ•˜๋‹ค.


๐Ÿš€ Sphinx ์„ค์น˜์™€ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณด์ž. ๋จผ์ € Sphinx๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค.
๊ฐ€์ƒํ™˜๊ฒฝ์„ ๋งŒ๋“ค๊ณ  ์‹œ์ž‘ํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค. ์™œ๋ƒ๊ณ ? ๋‚˜์ค‘์— ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ฒ„์ „์„ ์“ธ ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ๊นŒ. ๐Ÿ”ง

๐Ÿ“ฆ ์„ค์น˜ ๊ณผ์ •

# ๊ฐ€์ƒํ™˜๊ฒฝ ์ƒ์„ฑ (์„ ํƒ์‚ฌํ•ญ์ด์ง€๋งŒ ๊ฐ•๋ ฅ ์ถ”์ฒœ)
python -m venv myenv
source myenv/bin/activate  # Windows: myenv\Scripts\activate

# Sphinx ์„ค์น˜
pip install sphinx

# ์ถ”๊ฐ€๋กœ ์œ ์šฉํ•œ ํŒจํ‚ค์ง€๋“ค
pip install sphinx-rtd-theme  # Read the Docs ํ…Œ๋งˆ
pip install sphinx-autodoc-typehints  # ํƒ€์ž… ํžŒํŠธ ์ง€์›

์„ค์น˜๊ฐ€ ๋๋‚ฌ์œผ๋ฉด ์ด์ œ ๋ฌธ์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ์ฐจ๋ก€๋‹ค. ๐ŸŽฌ

# ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ
mkdir docs
cd docs
sphinx-quickstart
๐Ÿ’ก Quickstart ํŒ sphinx-quickstart๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์—ฌ๋Ÿฌ ์งˆ๋ฌธ์ด ๋‚˜์˜จ๋‹ค.
๋Œ€๋ถ€๋ถ„ ๊ธฐ๋ณธ๊ฐ’(์—”ํ„ฐ)์œผ๋กœ ์ง„ํ–‰ํ•ด๋„ ๋˜์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€๋Š” ์ฃผ์˜ํ•ด์„œ ๋‹ตํ•˜์ž:

- "Separate source and build directories?" โ†’ y (์†Œ์Šค์™€ ๋นŒ๋“œ ๋ถ„๋ฆฌ)
- "Project name" โ†’ ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ ์ž…๋ ฅ
- "Author name(s)" โ†’ ๋‹น์‹ ์˜ ์ด๋ฆ„
- "autodoc" โ†’ y (์ž๋™ ๋ฌธ์„œํ™” ํ™œ์„ฑํ™”)

์ด์ œ docs ๋””๋ ‰ํ† ๋ฆฌ ์•ˆ์— ์—ฌ๋Ÿฌ ํŒŒ์ผ์ด ์ƒ์„ฑ๋์„ ๊ฑฐ๋‹ค.
์ฃผ์š” ํŒŒ์ผ๋“ค์„ ์‚ดํŽด๋ณด์ž:

ํŒŒ์ผ/๋””๋ ‰ํ† ๋ฆฌ ์—ญํ• 
conf.py Sphinx ์„ค์ • ํŒŒ์ผ (๊ฐ€์žฅ ์ค‘์š”!)
index.rst ๋ฌธ์„œ์˜ ๋ฉ”์ธ ํŽ˜์ด์ง€
_build/ ์ƒ์„ฑ๋œ ๋ฌธ์„œ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ณณ
_static/ CSS, ์ด๋ฏธ์ง€ ๋“ฑ ์ •์  ํŒŒ์ผ
_templates/ ์ปค์Šคํ…€ ํ…œํ”Œ๋ฆฟ

๐Ÿ“ Docstring ์ž‘์„ฑ์˜ ๊ธฐ์ˆ 

Sphinx๊ฐ€ ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋ ค๋ฉด, ์ฝ”๋“œ์— ์ œ๋Œ€๋กœ ๋œ docstring์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. โœ๏ธ
Docstring์€ ํ•จ์ˆ˜, ํด๋ž˜์Šค, ๋ชจ๋“ˆ ๋ฐ”๋กœ ์•„๋ž˜์— ์ž‘์„ฑํ•˜๋Š” ์„ค๋ช… ๋ฌธ์ž์—ด์ด๋‹ค.

Python์—๋Š” ์—ฌ๋Ÿฌ docstring ์Šคํƒ€์ผ์ด ์žˆ๋Š”๋ฐ, Sphinx์™€ ๊ฐ€์žฅ ์ž˜ ๋งž๋Š” ๊ฑด ๋‹ค์Œ ์„ธ ๊ฐ€์ง€๋‹ค:

1๏ธโƒฃ Google Style (๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ์Œ)

def calculate_total(price, quantity, discount=0):
    """์ƒํ’ˆ์˜ ์ตœ์ข… ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    
    ์ด ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ ๊ฐ€๊ฒฉ์— ์ˆ˜๋Ÿ‰์„ ๊ณฑํ•˜๊ณ  ํ• ์ธ์„ ์ ์šฉํ•˜์—ฌ
    ์ตœ์ข… ๊ฒฐ์ œ ๊ธˆ์•ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    
    Args:
        price (float): ์ƒํ’ˆ์˜ ๋‹จ๊ฐ€
        quantity (int): ๊ตฌ๋งค ์ˆ˜๋Ÿ‰
        discount (float, optional): ํ• ์ธ์œจ (0~1 ์‚ฌ์ด). ๊ธฐ๋ณธ๊ฐ’์€ 0.
    
    Returns:
        float: ํ• ์ธ์ด ์ ์šฉ๋œ ์ตœ์ข… ๊ฐ€๊ฒฉ
    
    Raises:
        ValueError: price๋‚˜ quantity๊ฐ€ ์Œ์ˆ˜์ผ ๊ฒฝ์šฐ
        
    Examples:
        >>> calculate_total(1000, 2, 0.1)
        1800.0
        
        >>> calculate_total(500, 3)
        1500.0
    """
    if price < 0 or quantity < 0:
        raise ValueError("๊ฐ€๊ฒฉ๊ณผ ์ˆ˜๋Ÿ‰์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    
    subtotal = price * quantity
    return subtotal * (1 - discount)

2๏ธโƒฃ NumPy Style (๊ณผํ•™ ๊ณ„์‚ฐ ๋ถ„์•ผ์—์„œ ์„ ํ˜ธ)

def calculate_total(price, quantity, discount=0):
    """
    ์ƒํ’ˆ์˜ ์ตœ์ข… ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    
    ์ด ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ ๊ฐ€๊ฒฉ์— ์ˆ˜๋Ÿ‰์„ ๊ณฑํ•˜๊ณ  ํ• ์ธ์„ ์ ์šฉํ•˜์—ฌ
    ์ตœ์ข… ๊ฒฐ์ œ ๊ธˆ์•ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    
    Parameters
    ----------
    price : float
        ์ƒํ’ˆ์˜ ๋‹จ๊ฐ€
    quantity : int
        ๊ตฌ๋งค ์ˆ˜๋Ÿ‰
    discount : float, optional
        ํ• ์ธ์œจ (0~1 ์‚ฌ์ด), ๊ธฐ๋ณธ๊ฐ’์€ 0
    
    Returns
    -------
    float
        ํ• ์ธ์ด ์ ์šฉ๋œ ์ตœ์ข… ๊ฐ€๊ฒฉ
    
    Raises
    ------
    ValueError
        price๋‚˜ quantity๊ฐ€ ์Œ์ˆ˜์ผ ๊ฒฝ์šฐ
    
    Examples
    --------
    >>> calculate_total(1000, 2, 0.1)
    1800.0
    """
    if price < 0 or quantity < 0:
        raise ValueError("๊ฐ€๊ฒฉ๊ณผ ์ˆ˜๋Ÿ‰์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    
    subtotal = price * quantity
    return subtotal * (1 - discount)

3๏ธโƒฃ Sphinx Style (์ „ํ†ต์ ์ธ ๋ฐฉ์‹)

def calculate_total(price, quantity, discount=0):
    """์ƒํ’ˆ์˜ ์ตœ์ข… ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    
    ์ด ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ ๊ฐ€๊ฒฉ์— ์ˆ˜๋Ÿ‰์„ ๊ณฑํ•˜๊ณ  ํ• ์ธ์„ ์ ์šฉํ•˜์—ฌ
    ์ตœ์ข… ๊ฒฐ์ œ ๊ธˆ์•ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    
    :param price: ์ƒํ’ˆ์˜ ๋‹จ๊ฐ€
    :type price: float
    :param quantity: ๊ตฌ๋งค ์ˆ˜๋Ÿ‰
    :type quantity: int
    :param discount: ํ• ์ธ์œจ (0~1 ์‚ฌ์ด), ๊ธฐ๋ณธ๊ฐ’์€ 0
    :type discount: float
    :return: ํ• ์ธ์ด ์ ์šฉ๋œ ์ตœ์ข… ๊ฐ€๊ฒฉ
    :rtype: float
    :raises ValueError: price๋‚˜ quantity๊ฐ€ ์Œ์ˆ˜์ผ ๊ฒฝ์šฐ
    
    Example::
    
        >>> calculate_total(1000, 2, 0.1)
        1800.0
    """
    if price < 0 or quantity < 0:
        raise ValueError("๊ฐ€๊ฒฉ๊ณผ ์ˆ˜๋Ÿ‰์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    
    subtotal = price * quantity
    return subtotal * (1 - discount)
๐ŸŽจ ์–ด๋–ค ์Šคํƒ€์ผ์„ ์„ ํƒํ• ๊นŒ? ๊ฐœ์ธ์ ์œผ๋กœ๋Š” Google Style์„ ์ถ”์ฒœํ•œ๋‹ค. ๐Ÿ‘
๊ฐ€๋…์„ฑ์ด ์ข‹๊ณ , ์ž‘์„ฑํ•˜๊ธฐ๋„ ํŽธํ•˜๋ฉฐ, ๋งŽ์€ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ํŒ€์ด๋‚˜ ํ”„๋กœ์ ํŠธ์— ์ด๋ฏธ ์ •ํ•ด์ง„ ์Šคํƒ€์ผ์ด ์žˆ๋‹ค๋ฉด ๊ทธ๊ฑธ ๋”ฐ๋ฅด๋Š” ๊ฒŒ ์ตœ์„ ์ด๋‹ค.
์ผ๊ด€์„ฑ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ˆ๊นŒ!

๐Ÿ—๏ธ ํด๋ž˜์Šค ๋ฌธ์„œํ™” ์˜ˆ์ œ

class ShoppingCart:
    """์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์˜ ์žฅ๋ฐ”๊ตฌ๋‹ˆ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
    
    ์ด ํด๋ž˜์Šค๋Š” ์ƒํ’ˆ ์ถ”๊ฐ€, ์ œ๊ฑฐ, ์ด์•ก ๊ณ„์‚ฐ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
    
    Attributes:
        items (list): ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ๋‹ด๊ธด ์ƒํ’ˆ ๋ชฉ๋ก
        user_id (str): ์‚ฌ์šฉ์ž ์‹๋ณ„์ž
        
    Examples:
        >>> cart = ShoppingCart("user123")
        >>> cart.add_item("๋…ธํŠธ๋ถ", 1500000, 1)
        >>> cart.get_total()
        1500000
    """
    
    def __init__(self, user_id):
        """ShoppingCart ์ธ์Šคํ„ด์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            user_id (str): ์‚ฌ์šฉ์ž ์‹๋ณ„์ž
        """
        self.items = []
        self.user_id = user_id
    
    def add_item(self, name, price, quantity=1):
        """์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ƒํ’ˆ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            name (str): ์ƒํ’ˆ๋ช…
            price (float): ์ƒํ’ˆ ๊ฐ€๊ฒฉ
            quantity (int, optional): ์ˆ˜๋Ÿ‰. ๊ธฐ๋ณธ๊ฐ’์€ 1.
            
        Returns:
            bool: ์ถ”๊ฐ€ ์„ฑ๊ณต ์—ฌ๋ถ€
            
        Raises:
            ValueError: ๊ฐ€๊ฒฉ์ด๋‚˜ ์ˆ˜๋Ÿ‰์ด ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ
        """
        if price <= 0 or quantity <= 0:
            raise ValueError("๊ฐ€๊ฒฉ๊ณผ ์ˆ˜๋Ÿ‰์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
        
        self.items.append({
            'name': name,
            'price': price,
            'quantity': quantity
        })
        return True
    
    def get_total(self):
        """์žฅ๋ฐ”๊ตฌ๋‹ˆ์˜ ์ด ๊ธˆ์•ก์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        
        Returns:
            float: ๋ชจ๋“  ์ƒํ’ˆ์˜ ์ด ๊ฐ€๊ฒฉ
        """
        return sum(item['price'] * item['quantity'] for item in self.items)
Docstring ๊ตฌ์กฐ ํ•œ๋ˆˆ์— ๋ณด๊ธฐ ์ฝ”๋“œ (Python) def my_function(param1, param2): """ํ•จ์ˆ˜ ์„ค๋ช…""" Args: ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช… Returns: ๋ฐ˜ํ™˜๊ฐ’ ์„ค๋ช… Raises: ์˜ˆ์™ธ ์„ค๋ช… Examples: ์‚ฌ์šฉ ์˜ˆ์ œ >>> my_function(1, 2) ์‹ค์ œ ์ฝ”๋“œ ๊ตฌํ˜„ return param1 + param2 ์ƒ์„ฑ๋œ ๋ฌธ์„œ (HTML) my_function(param1, param2) ํ•จ์ˆ˜ ์„ค๋ช… Parameters: โ€ข param1: ์„ค๋ช… โ€ข param2: ์„ค๋ช… Returns: ๋ฐ˜ํ™˜๊ฐ’ ์„ค๋ช… Example: >>> my_function(1, 2) 3 โ†’

โš™๏ธ conf.py ์„ค์ • ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

conf.py๋Š” Sphinx์˜ ๋‘๋‡Œ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ๐Ÿง 
์—ฌ๊ธฐ์„œ ๋ชจ๋“  ์„ค์ •์„ ๊ด€๋ฆฌํ•˜๋‹ˆ๊นŒ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ๋„˜์–ด๊ฐ€์ž.

๐Ÿ”ง ๊ธฐ๋ณธ ์„ค์ •

# conf.py

# ํ”„๋กœ์ ํŠธ ์ •๋ณด
project = 'My Awesome API'
copyright = '2024, Your Name'
author = 'Your Name'
release = '1.0.0'

# ์ผ๋ฐ˜ ์„ค์ •
extensions = [
    'sphinx.ext.autodoc',      # ์ž๋™ ๋ฌธ์„œํ™”
    'sphinx.ext.napoleon',     # Google/NumPy ์Šคํƒ€์ผ ์ง€์›
    'sphinx.ext.viewcode',     # ์†Œ์Šค ์ฝ”๋“œ ๋งํฌ
    'sphinx.ext.intersphinx',  # ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ ์ฐธ์กฐ
    'sphinx.ext.todo',         # TODO ํ•ญ๋ชฉ ์ง€์›
    'sphinx.ext.coverage',     # ๋ฌธ์„œํ™” ์ปค๋ฒ„๋ฆฌ์ง€ ์ฒดํฌ
]

# ํ…œํ”Œ๋ฆฟ ๊ฒฝ๋กœ
templates_path = ['_templates']

# ์ œ์™ธํ•  ํŒจํ„ด
exclude_patterns = []

# HTML ์ถœ๋ ฅ ์˜ต์…˜
html_theme = 'sphinx_rtd_theme'  # Read the Docs ํ…Œ๋งˆ
html_static_path = ['_static']
html_theme_options = {
    'navigation_depth': 4,
    'collapse_navigation': False,
    'sticky_navigation': True,
    'includehidden': True,
}

๐ŸŽจ autodoc ํ™•์žฅ ์„ค์ •

autodoc์€ Python ์ฝ”๋“œ์—์„œ ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ถ”์ถœํ•˜๋Š” ํ•ต์‹ฌ ํ™•์žฅ์ด๋‹ค. ๐Ÿ”
์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๋ ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค.

# conf.py์— ์ถ”๊ฐ€

import os
import sys

# ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ๋ฅผ Python ๊ฒฝ๋กœ์— ์ถ”๊ฐ€
sys.path.insert(0, os.path.abspath('..'))

# autodoc ์„ค์ •
autodoc_default_options = {
    'members': True,           # ๋ชจ๋“  ๋ฉค๋ฒ„ ํฌํ•จ
    'member-order': 'bysource',  # ์†Œ์Šค ์ฝ”๋“œ ์ˆœ์„œ๋Œ€๋กœ
    'special-members': '__init__',  # __init__ ํฌํ•จ
    'undoc-members': True,     # ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ ๋ฉค๋ฒ„๋„ ํฌํ•จ
    'exclude-members': '__weakref__'  # ์ œ์™ธํ•  ๋ฉค๋ฒ„
}

# ํƒ€์ž… ํžŒํŠธ ํ‘œ์‹œ
autodoc_typehints = 'description'

# Napoleon ์„ค์ • (Google/NumPy ์Šคํƒ€์ผ)
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = True
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = True
napoleon_use_admonition_for_notes = True
napoleon_use_admonition_for_references = True
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ sys.path.insert(0, os.path.abspath('..')) ๋ถ€๋ถ„์ด ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค!
์ด๊ฒŒ ์—†์œผ๋ฉด Sphinx๊ฐ€ ๋‹น์‹ ์˜ ๋ชจ๋“ˆ์„ ์ฐพ์ง€ ๋ชปํ•ด์„œ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค. ๐Ÿ˜ฑ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์— ๋”ฐ๋ผ ๊ฒฝ๋กœ๋ฅผ ์กฐ์ •ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ src/ ๋””๋ ‰ํ† ๋ฆฌ์— ์žˆ๋‹ค๋ฉด:
sys.path.insert(0, os.path.abspath('../src'))

๐ŸŒˆ ํ…Œ๋งˆ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

๊ธฐ๋ณธ ํ…Œ๋งˆ๋„ ๊ดœ์ฐฎ์ง€๋งŒ, ์ข€ ๋” ๋ฉ‹์ง€๊ฒŒ ๊พธ๋ฏธ๊ณ  ์‹ถ๋‹ค๋ฉด? ๐ŸŽจ

# ์ธ๊ธฐ ์žˆ๋Š” ํ…Œ๋งˆ๋“ค
# pip install sphinx-rtd-theme
html_theme = 'sphinx_rtd_theme'

# pip install pydata-sphinx-theme
html_theme = 'pydata_sphinx_theme'

# pip install furo
html_theme = 'furo'

# ํ…Œ๋งˆ๋ณ„ ์˜ต์…˜ ์˜ˆ์ œ (Read the Docs)
html_theme_options = {
    'logo_only': False,
    'display_version': True,
    'prev_next_buttons_location': 'bottom',
    'style_external_links': True,
    'vcs_pageview_mode': '',
    'style_nav_header_background': '#2980B9',
    'collapse_navigation': True,
    'sticky_navigation': True,
    'navigation_depth': 4,
    'includehidden': True,
    'titles_only': False
}

# ์ปค์Šคํ…€ CSS ์ถ”๊ฐ€
html_css_files = [
    'custom.css',
]

# ๋กœ๊ณ ์™€ ํŒŒ๋น„์ฝ˜
html_logo = '_static/logo.png'
html_favicon = '_static/favicon.ico'

๐Ÿ“„ reStructuredText (RST) ๊ธฐ์ดˆ

Sphinx๋Š” reStructuredText(RST)๋ผ๋Š” ๋งˆํฌ์—… ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๐Ÿ“
๋งˆํฌ๋‹ค์šด๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ ๋” ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ๊ธฐ๋ณธ ๋ฌธ๋ฒ•์„ ์•Œ์•„๋ณด์ž.

โœ๏ธ ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

์ œ๋ชฉ ๋ ˆ๋ฒจ 1
===========

์ œ๋ชฉ ๋ ˆ๋ฒจ 2
-----------

์ œ๋ชฉ ๋ ˆ๋ฒจ 3
~~~~~~~~~~~

**๊ตต์€ ๊ธ€์”จ**
*๊ธฐ์šธ์ž„*
``์ฝ”๋“œ``

- ์ˆœ์„œ ์—†๋Š” ๋ชฉ๋ก
- ํ•ญ๋ชฉ 2
  
  - ์ค‘์ฒฉ ํ•ญ๋ชฉ

1. ์ˆœ์„œ ์žˆ๋Š” ๋ชฉ๋ก
2. ํ•ญ๋ชฉ 2

๋งํฌ: `Python ๊ณต์‹ ์‚ฌ์ดํŠธ <https://python.org>`_

์ด๋ฏธ์ง€:

.. image:: path/to/image.png
   :width: 400px
   :alt: ๋Œ€์ฒด ํ…์ŠคํŠธ

์ฝ”๋“œ ๋ธ”๋ก:

.. code-block:: python

   def hello():
       print("Hello, World!")

์ฃผ์„:

.. note::
   ์ด๊ฒƒ์€ ๋…ธํŠธ์ž…๋‹ˆ๋‹ค.

.. warning::
   ์ด๊ฒƒ์€ ๊ฒฝ๊ณ ์ž…๋‹ˆ๋‹ค.

.. tip::
   ์ด๊ฒƒ์€ ํŒ์ž…๋‹ˆ๋‹ค.

๐Ÿ”— API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ

์ด์ œ ์ง„์งœ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ค. ์ฝ”๋“œ์—์„œ ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•! โœจ

API ๋ ˆํผ๋Ÿฐ์Šค
============

mymodule ๋ชจ๋“ˆ
-------------

.. automodule:: mymodule
   :members:
   :undoc-members:
   :show-inheritance:

ShoppingCart ํด๋ž˜์Šค
-------------------

.. autoclass:: mymodule.ShoppingCart
   :members:
   :special-members: __init__
   :private-members:

calculate_total ํ•จ์ˆ˜
--------------------

.. autofunction:: mymodule.calculate_total
๐Ÿ’ก Pro Tip: ์ž๋™ ์ƒ์„ฑ ๋ช…๋ น์–ด ๋ชจ๋“  ๋ชจ๋“ˆ์„ ์ผ์ผ์ด ์ž‘์„ฑํ•˜๊ธฐ ๊ท€์ฐฎ๋‹ค๋ฉด sphinx-apidoc์„ ์‚ฌ์šฉํ•˜์ž!

sphinx-apidoc -o docs/source mypackage

์ด ๋ช…๋ น์–ด๋Š” ํŒจํ‚ค์ง€์˜ ๋ชจ๋“  ๋ชจ๋“ˆ์— ๋Œ€ํ•œ RST ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ค€๋‹ค. ๐Ÿš€

๐Ÿ“š ๋ชฉ์ฐจ ๊ตฌ์กฐ ๋งŒ๋“ค๊ธฐ

index.rst๋Š” ๋ฌธ์„œ์˜ ๋ฉ”์ธ ํŽ˜์ด์ง€์ด์ž ๋ชฉ์ฐจ ์—ญํ• ์„ ํ•œ๋‹ค.

Welcome to My Project's documentation!
======================================

์ด ํ”„๋กœ์ ํŠธ๋Š” ๋ฉ‹์ง„ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

.. toctree::
   :maxdepth: 2
   :caption: ๋ชฉ์ฐจ:

   installation
   quickstart
   api/modules
   examples
   contributing
   changelog

์†Œ๊ฐœ
----

์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

* ๊ธฐ๋Šฅ 1
* ๊ธฐ๋Šฅ 2
* ๊ธฐ๋Šฅ 3

๋น ๋ฅธ ์‹œ์ž‘
---------

.. code-block:: python

   from mypackage import MyClass
   
   obj = MyClass()
   result = obj.do_something()

์ธ๋ฑ์Šค์™€ ํ…Œ์ด๋ธ”
===============

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
๋ฌธ์„œ ๋นŒ๋“œ ํ”„๋กœ์„ธ์Šค ์†Œ์Šค ํŒŒ์ผ index.rst ๋ฉ”์ธ ํŽ˜์ด์ง€ api/modules.rst API ๋ฌธ์„œ conf.py ์„ค์ • ํŒŒ์ผ Python ์ฝ”๋“œ Docstrings ๋นŒ๋“œ ๋ช…๋ น์–ด make html make latexpdf make epub make clean ๋‹ค์–‘ํ•œ ํฌ๋งท ์ง€์› Sphinx ์—”์ง„ ํŒŒ์‹ฑ RST ๋ถ„์„ ๋ณ€ํ™˜ HTML ์ƒ์„ฑ ์Šคํƒ€์ผ๋ง ํ…Œ๋งˆ ์ ์šฉ ์ถœ๋ ฅ ๊ฒฐ๊ณผ HTML ์›น ๋ธŒ๋ผ์šฐ์ €์šฉ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ PDF ์ธ์‡„์šฉ ์˜คํ”„๋ผ์ธ ๋ฐฐํฌ ์ „๋ฌธ์ ์ธ ์™ธ๊ด€ ePub ์ „์ž์ฑ… ๋ฆฌ๋”์šฉ ๋ชจ๋ฐ”์ผ ์ตœ์ ํ™” ์œ ์—ฐํ•œ ๋ ˆ์ด์•„์›ƒ Man Pages Unix/Linux์šฉ ํ„ฐ๋ฏธ๋„์—์„œ ์กฐํšŒ ์‹œ์Šคํ…œ ํ†ตํ•ฉ

๐Ÿ—๏ธ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋Š”์ง€ ๋ณด์ž. ๐Ÿ› ๏ธ
์ „ํ˜•์ ์ธ Python ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

my_project/
โ”œโ”€โ”€ docs/
โ”‚   โ”œโ”€โ”€ source/
โ”‚   โ”‚   โ”œโ”€โ”€ conf.py
โ”‚   โ”‚   โ”œโ”€โ”€ index.rst
โ”‚   โ”‚   โ”œโ”€โ”€ installation.rst
โ”‚   โ”‚   โ”œโ”€โ”€ quickstart.rst
โ”‚   โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ modules.rst
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ mypackage.rst
โ”‚   โ”‚   โ””โ”€โ”€ _static/
โ”‚   โ”‚       โ””โ”€โ”€ custom.css
โ”‚   โ”œโ”€โ”€ build/
โ”‚   โ”‚   โ””โ”€โ”€ html/
โ”‚   โ””โ”€โ”€ Makefile
โ”œโ”€โ”€ mypackage/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ core.py
โ”‚   โ”œโ”€โ”€ utils.py
โ”‚   โ””โ”€โ”€ models/
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ””โ”€โ”€ user.py
โ”œโ”€โ”€ tests/
โ”‚   โ””โ”€โ”€ test_core.py
โ”œโ”€โ”€ setup.py
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ README.md

๐Ÿ“ฆ ์‹ค์ œ ๋ชจ๋“ˆ ์˜ˆ์ œ

๊ฐ„๋‹จํ•œ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ์‹ค์ œ๋กœ ๋ฌธ์„œํ™”๊ฐ€ ์–ด๋–ป๊ฒŒ ๋˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

# mypackage/models/user.py

from typing import Optional, List
from datetime import datetime

class User:
    """์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
    
    ์ด ํด๋ž˜์Šค๋Š” ์‚ฌ์šฉ์ž์˜ ๊ธฐ๋ณธ ์ •๋ณด์™€ ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•˜๋ฉฐ,
    ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์ถ”์ƒํ™”ํ•ฉ๋‹ˆ๋‹ค.
    
    Attributes:
        user_id (str): ์‚ฌ์šฉ์ž ๊ณ ์œ  ์‹๋ณ„์ž
        username (str): ์‚ฌ์šฉ์ž๋ช…
        email (str): ์ด๋ฉ”์ผ ์ฃผ์†Œ
        created_at (datetime): ๊ณ„์ • ์ƒ์„ฑ ์‹œ๊ฐ„
        is_active (bool): ๊ณ„์ • ํ™œ์„ฑํ™” ์ƒํƒœ
        roles (List[str]): ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๋ชฉ๋ก
        
    Examples:
        >>> user = User("user123", "john_doe", "john@example.com")
        >>> user.add_role("admin")
        >>> user.has_role("admin")
        True
        
    Note:
        ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋ณ„๋„์˜ ๋ณด์•ˆ ๋ชจ๋“ˆ์—์„œ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
        ์ด ํด๋ž˜์Šค๋Š” ์ธ์ฆ ์ •๋ณด๋ฅผ ์ง์ ‘ ๋‹ค๋ฃจ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    """
    
    def __init__(
        self, 
        user_id: str, 
        username: str, 
        email: str,
        is_active: bool = True
    ):
        """User ์ธ์Šคํ„ด์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            user_id: ์‚ฌ์šฉ์ž ๊ณ ์œ  ์‹๋ณ„์ž (UUID ๊ถŒ์žฅ)
            username: ์‚ฌ์šฉ์ž๋ช… (3-20์ž, ์˜๋ฌธ/์ˆซ์ž/์–ธ๋”์Šค์ฝ”์–ด)
            email: ์ด๋ฉ”์ผ ์ฃผ์†Œ (์œ ํšจํ•œ ํ˜•์‹์ด์–ด์•ผ ํ•จ)
            is_active: ๊ณ„์ • ํ™œ์„ฑํ™” ์ƒํƒœ (๊ธฐ๋ณธ๊ฐ’: True)
            
        Raises:
            ValueError: username์ด๋‚˜ email ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์„ ๊ฒฝ์šฐ
        """
        self.user_id = user_id
        self.username = username
        self.email = email
        self.created_at = datetime.now()
        self.is_active = is_active
        self.roles: List[str] = []
        
    def add_role(self, role: str) -> bool:
        """์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            role: ์ถ”๊ฐ€ํ•  ๊ถŒํ•œ ์ด๋ฆ„ (์˜ˆ: 'admin', 'editor', 'viewer')
            
        Returns:
            ๊ถŒํ•œ ์ถ”๊ฐ€ ์„ฑ๊ณต ์—ฌ๋ถ€
            
        Raises:
            ValueError: ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ถŒํ•œ์ผ ๊ฒฝ์šฐ
            
        Examples:
            >>> user.add_role("editor")
            True
            >>> user.add_role("editor")  # ์ค‘๋ณต
            ValueError: Role 'editor' already exists
        """
        if role in self.roles:
            raise ValueError(f"Role '{role}' already exists")
        self.roles.append(role)
        return True
    
    def remove_role(self, role: str) -> bool:
        """์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            role: ์ œ๊ฑฐํ•  ๊ถŒํ•œ ์ด๋ฆ„
            
        Returns:
            ๊ถŒํ•œ ์ œ๊ฑฐ ์„ฑ๊ณต ์—ฌ๋ถ€
            
        Raises:
            ValueError: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ถŒํ•œ์ผ ๊ฒฝ์šฐ
        """
        if role not in self.roles:
            raise ValueError(f"Role '{role}' not found")
        self.roles.remove(role)
        return True
    
    def has_role(self, role: str) -> bool:
        """์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            role: ํ™•์ธํ•  ๊ถŒํ•œ ์ด๋ฆ„
            
        Returns:
            ๊ถŒํ•œ ๋ณด์œ  ์—ฌ๋ถ€
            
        Examples:
            >>> user.add_role("admin")
            >>> user.has_role("admin")
            True
            >>> user.has_role("superuser")
            False
        """
        return role in self.roles
    
    def deactivate(self) -> None:
        """์‚ฌ์šฉ์ž ๊ณ„์ •์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
        
        ๋น„ํ™œ์„ฑํ™”๋œ ๊ณ„์ •์€ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์—†์œผ๋ฉฐ,
        ๋ชจ๋“  ์„ธ์…˜์ด ์ฆ‰์‹œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
        
        Warning:
            ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ์ง„ํ–‰ ์ค‘์ธ ์ž‘์—…์ด
            ๋ชจ๋‘ ์ค‘๋‹จ๋˜๋ฏ€๋กœ ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
        """
        self.is_active = False
    
    def activate(self) -> None:
        """์‚ฌ์šฉ์ž ๊ณ„์ •์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
        
        ๋น„ํ™œ์„ฑํ™”๋œ ๊ณ„์ •์„ ๋‹ค์‹œ ํ™œ์„ฑํ™”ํ•˜์—ฌ
        ์ •์ƒ์ ์ธ ์„œ๋น„์Šค ์ด์šฉ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
        """
        self.is_active = True
    
    def to_dict(self) -> dict:
        """์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        
        Returns:
            ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ๋”•์…”๋„ˆ๋ฆฌ
            
        Examples:
            >>> user.to_dict()
            {
                'user_id': 'user123',
                'username': 'john_doe',
                'email': 'john@example.com',
                'is_active': True,
                'roles': ['admin', 'editor']
            }
        """
        return {
            'user_id': self.user_id,
            'username': self.username,
            'email': self.email,
            'created_at': self.created_at.isoformat(),
            'is_active': self.is_active,
            'roles': self.roles
        }

๐Ÿ”ง ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์˜ˆ์ œ

# mypackage/utils.py

from typing import List, Optional
import re

def validate_email(email: str) -> bool:
    """์ด๋ฉ”์ผ ์ฃผ์†Œ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
    
    RFC 5322 ํ‘œ์ค€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฉ”์ผ ํ˜•์‹์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
    ๋‹จ, ์™„๋ฒฝํ•œ ๊ฒ€์ฆ์€ ์•„๋‹ˆ๋ฉฐ ์ผ๋ฐ˜์ ์ธ ์ผ€์ด์Šค๋ฅผ ์ปค๋ฒ„ํ•ฉ๋‹ˆ๋‹ค.
    
    Args:
        email: ๊ฒ€์ฆํ•  ์ด๋ฉ”์ผ ์ฃผ์†Œ
        
    Returns:
        ์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด๋ฉด True, ์•„๋‹ˆ๋ฉด False
        
    Examples:
        >>> validate_email("user@example.com")
        True
        >>> validate_email("invalid.email")
        False
        >>> validate_email("user@sub.domain.com")
        True
        
    Note:
        ์ด ํ•จ์ˆ˜๋Š” ํ˜•์‹๋งŒ ๊ฒ€์ฆํ•˜๋ฉฐ, ์‹ค์ œ ์ด๋ฉ”์ผ ์ฃผ์†Œ์˜
        ์กด์žฌ ์—ฌ๋ถ€๋Š” ํ™•์ธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    """
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

def validate_username(username: str) -> bool:
    """์‚ฌ์šฉ์ž๋ช…์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
    
    ์‚ฌ์šฉ์ž๋ช…์€ ๋‹ค์Œ ๊ทœ์น™์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค:
    - 3์ž ์ด์ƒ 20์ž ์ดํ•˜
    - ์˜๋ฌธ์ž, ์ˆซ์ž, ์–ธ๋”์Šค์ฝ”์–ด๋งŒ ํ—ˆ์šฉ
    - ์˜๋ฌธ์ž๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•จ
    
    Args:
        username: ๊ฒ€์ฆํ•  ์‚ฌ์šฉ์ž๋ช…
        
    Returns:
        ์œ ํšจํ•œ ์‚ฌ์šฉ์ž๋ช…์ด๋ฉด True, ์•„๋‹ˆ๋ฉด False
        
    Examples:
        >>> validate_username("john_doe")
        True
        >>> validate_username("ab")  # ๋„ˆ๋ฌด ์งง์Œ
        False
        >>> validate_username("123user")  # ์ˆซ์ž๋กœ ์‹œ์ž‘
        False
    """
    if not 3 <= len(username) <= 20:
        return False
    pattern = r'^[a-zA-Z][a-zA-Z0-9_]*$'
    return bool(re.match(pattern, username))

def search_users(
    users: List['User'], 
    query: str, 
    field: str = 'username'
) -> List['User']:
    """์‚ฌ์šฉ์ž ๋ชฉ๋ก์—์„œ ํŠน์ • ์กฐ๊ฑด์— ๋งž๋Š” ์‚ฌ์šฉ์ž๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
    
    ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๋Š” ๋ถ€๋ถ„ ์ผ์น˜ ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    
    Args:
        users: ๊ฒ€์ƒ‰ํ•  ์‚ฌ์šฉ์ž ๋ชฉ๋ก
        query: ๊ฒ€์ƒ‰์–ด
        field: ๊ฒ€์ƒ‰ํ•  ํ•„๋“œ ('username', 'email', 'user_id')
        
    Returns:
        ๊ฒ€์ƒ‰ ์กฐ๊ฑด์— ๋งž๋Š” ์‚ฌ์šฉ์ž ๋ชฉ๋ก
        
    Raises:
        ValueError: ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ•„๋“œ๋ฅผ ์ง€์ •ํ•œ ๊ฒฝ์šฐ
        
    Examples:
        >>> users = [user1, user2, user3]
        >>> results = search_users(users, "john", "username")
        >>> len(results)
        2
        
    See Also:
        :func:`filter_active_users`: ํ™œ์„ฑ ์‚ฌ์šฉ์ž๋งŒ ํ•„ํ„ฐ๋ง
    """
    if field not in ['username', 'email', 'user_id']:
        raise ValueError(f"Unsupported field: {field}")
    
    query_lower = query.lower()
    return [
        user for user in users 
        if query_lower in getattr(user, field).lower()
    ]

def filter_active_users(users: List['User']) -> List['User']:
    """ํ™œ์„ฑํ™”๋œ ์‚ฌ์šฉ์ž๋งŒ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.
    
    Args:
        users: ํ•„ํ„ฐ๋งํ•  ์‚ฌ์šฉ์ž ๋ชฉ๋ก
        
    Returns:
        ํ™œ์„ฑ ์ƒํƒœ์ธ ์‚ฌ์šฉ์ž ๋ชฉ๋ก
        
    Examples:
        >>> all_users = [active_user, inactive_user, active_user2]
        >>> active_only = filter_active_users(all_users)
        >>> len(active_only)
        2
    """
    return [user for user in users if user.is_active]

๐ŸŽจ ๋ฌธ์„œ ๋นŒ๋“œํ•˜๊ธฐ

์ฝ”๋“œ์™€ docstring ์ž‘์„ฑ์ด ๋๋‚ฌ์œผ๋ฉด ์ด์ œ ์‹ค์ œ๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•  ์ฐจ๋ก€๋‹ค! ๐ŸŽ‰

๐Ÿ”จ ๋นŒ๋“œ ๋ช…๋ น์–ด

# docs ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™
cd docs

# HTML ๋ฌธ์„œ ์ƒ์„ฑ
make html

# Windows์—์„œ๋Š”
make.bat html

# ๋˜๋Š” ์ง์ ‘ sphinx-build ์‚ฌ์šฉ
sphinx-build -b html source build/html

# PDF ์ƒ์„ฑ (LaTeX ํ•„์š”)
make latexpdf

# ePub ์ƒ์„ฑ
make epub

# ๋นŒ๋“œ ๊ฒฐ๊ณผ ์‚ญ์ œ
make clean

๋นŒ๋“œ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด docs/build/html/index.html ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.
๋ธŒ๋ผ์šฐ์ €๋กœ ์—ด์–ด๋ณด๋ฉด ๋ฉ‹์ง„ ๋ฌธ์„œ๊ฐ€ ์™„์„ฑ๋˜์–ด ์žˆ์„ ๊ฑฐ๋‹ค! ๐ŸŒŸ

๐Ÿš€ ์ž๋™ ๋นŒ๋“œ ์„ค์ • ๋งค๋ฒˆ ์ˆ˜๋™์œผ๋กœ ๋นŒ๋“œํ•˜๊ธฐ ๊ท€์ฐฎ๋‹ค๋ฉด sphinx-autobuild๋ฅผ ์‚ฌ์šฉํ•˜์ž:

pip install sphinx-autobuild
sphinx-autobuild docs/source docs/build/html

์ด์ œ ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋นŒ๋“œ๋˜๊ณ  ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋œ๋‹ค! โšก

๐Ÿ› ํ”ํ•œ ์—๋Ÿฌ์™€ ํ•ด๊ฒฐ๋ฒ•

์—๋Ÿฌ ์›์ธ ํ•ด๊ฒฐ๋ฒ•
ModuleNotFoundError Python์ด ๋ชจ๋“ˆ์„ ์ฐพ์ง€ ๋ชปํ•จ conf.py์—์„œ sys.path ํ™•์ธ
WARNING: document isn't included toctree์— ํŒŒ์ผ์ด ์—†์Œ index.rst์˜ toctree์— ์ถ”๊ฐ€
WARNING: undefined label ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ฐธ์กฐ ๋ ˆ์ด๋ธ” ์ด๋ฆ„ ํ™•์ธ ๋˜๋Š” ์ƒ์„ฑ
Theme not found ํ…Œ๋งˆ๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์Œ pip install๋กœ ํ…Œ๋งˆ ์„ค์น˜
โš ๏ธ ์ฃผ์˜: Import ์—๋Ÿฌ autodoc์ด ๋ชจ๋“ˆ์„ importํ•  ๋•Œ ์‹ค์ œ๋กœ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค!
๋”ฐ๋ผ์„œ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ์—์„œ ๋ฌด๊ฑฐ์šด ์ž‘์—…(DB ์—ฐ๊ฒฐ, ํŒŒ์ผ ์ฝ๊ธฐ ๋“ฑ)์„ ํ•˜๋ฉด ๋นŒ๋“œ๊ฐ€ ๋А๋ ค์ง€๊ฑฐ๋‚˜ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ…: if __name__ == '__main__': ๋ธ”๋ก ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜,
conf.py์—์„œ autodoc_mock_imports๋กœ ํŠน์ • ๋ชจ๋“ˆ์„ mock ์ฒ˜๋ฆฌ

๐ŸŒ ๋ฌธ์„œ ๋ฐฐํฌํ•˜๊ธฐ

๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ด์ œ ์„ธ์ƒ์— ๊ณต๊ฐœํ•  ์ฐจ๋ก€๋‹ค! ๐Ÿš€
์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฐํฌ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๊ฒƒ๋“ค์„ ์†Œ๊ฐœํ•œ๋‹ค.

๐Ÿ“˜ Read the Docs

Read the Docs๋Š” ๋ฌด๋ฃŒ๋กœ ๋ฌธ์„œ๋ฅผ ํ˜ธ์ŠคํŒ…ํ•ด์ฃผ๋Š” ์ตœ๊ณ ์˜ ํ”Œ๋žซํผ์ด๋‹ค. ๐Ÿ’™
GitHub์™€ ์—ฐ๋™ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋นŒ๋“œ๋˜๊ณ  ๋ฐฐํฌ๋œ๋‹ค.

๐Ÿ”ง Read the Docs ์„ค์ •

1. .readthedocs.yaml ํŒŒ์ผ ์ƒ์„ฑ:

version: 2

build:
  os: ubuntu-22.04
  tools:
    python: "3.11"

sphinx:
  configuration: docs/source/conf.py

python:
  install:
    - requirements: docs/requirements.txt
    - method: pip
      path: .

2. docs/requirements.txt ์ƒ์„ฑ:

sphinx>=5.0
sphinx-rtd-theme
sphinx-autodoc-typehints

3. Read the Docs ์›น์‚ฌ์ดํŠธ์—์„œ ํ”„๋กœ์ ํŠธ import
4. ๋! ์ด์ œ ์ž๋™์œผ๋กœ ๋นŒ๋“œ๋œ๋‹ค โœจ

๐Ÿ“„ GitHub Pages

GitHub Pages๋กœ๋„ ์‰ฝ๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋‹ค.

# 1. gh-pages ๋ธŒ๋žœ์น˜ ์ƒ์„ฑ
git checkout --orphan gh-pages
git rm -rf .

# 2. ๋นŒ๋“œ๋œ HTML ํŒŒ์ผ ๋ณต์‚ฌ
cp -r docs/build/html/* .

# 3. ์ปค๋ฐ‹ ๋ฐ ํ‘ธ์‹œ
git add .
git commit -m "Deploy documentation"
git push origin gh-pages

# 4. GitHub ์ €์žฅ์†Œ ์„ค์ •์—์„œ Pages ํ™œ์„ฑํ™”
๐Ÿ’ก ์ž๋™ํ™” ํŒ: GitHub Actions

๋งค๋ฒˆ ์ˆ˜๋™์œผ๋กœ ๋ฐฐํฌํ•˜๊ธฐ ๊ท€์ฐฎ๋‹ค๋ฉด GitHub Actions๋ฅผ ์‚ฌ์šฉํ•˜์ž!

.github/workflows/docs.yml ์ƒ์„ฑ:
name: Deploy Documentation

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install sphinx sphinx-rtd-theme
        pip install -e .
    
    - name: Build documentation
      run: |
        cd docs
        make html
    
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./docs/build/html

๐Ÿณ Docker๋กœ ๋ฐฐํฌ

์ž์ฒด ์„œ๋ฒ„์— ๋ฐฐํฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด Docker๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# Dockerfile
FROM python:3.11-slim

WORKDIR /docs

COPY docs/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN cd docs && make html

# Nginx๋กœ ์„œ๋น™
FROM nginx:alpine
COPY --from=0 /docs/docs/build/html /usr/share/nginx/html

EXPOSE 80
# ๋นŒ๋“œ ๋ฐ ์‹คํ–‰
docker build -t my-docs .
docker run -d -p 8080:80 my-docs

# ์ด์ œ http://localhost:8080 ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ!
๋ฌธ์„œ ๋ฐฐํฌ ์˜ต์…˜ Read the Docs ๋ฌด๋ฃŒ ํ˜ธ์ŠคํŒ… ์ž๋™ ๋นŒ๋“œ ๋ฒ„์ „ ๊ด€๋ฆฌ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ์ปค์Šคํ…€ ๋„๋ฉ”์ธ ์ถ”์ฒœ ๋Œ€์ƒ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ ๋น ๋ฅธ ์‹œ์ž‘์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ GitHub Pages ๋ฌด๋ฃŒ ํ˜ธ์ŠคํŒ… GitHub ํ†ตํ•ฉ ์ˆ˜๋™ ๋นŒ๋“œ ํ•„์š” ์ปค์Šคํ…€ ๋„๋ฉ”์ธ ์ •์  ํŒŒ์ผ๋งŒ ๊ฐ€๋Šฅ ์ถ”์ฒœ ๋Œ€์ƒ GitHub ์‚ฌ์šฉ์ž ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ ์ž์ฒด ์„œ๋ฒ„ ์™„์ „ํ•œ ์ œ์–ด ํ”„๋ผ์ด๋น— ๊ฐ€๋Šฅ ์„œ๋ฒ„ ๊ด€๋ฆฌ ํ•„์š” ๋น„์šฉ ๋ฐœ์ƒ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ์ž์œ  ์ถ”์ฒœ ๋Œ€์ƒ ๊ธฐ์—…์šฉ ํ”„๋กœ์ ํŠธ ๋ณด์•ˆ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ

๐ŸŽฏ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๊ณผ ํŒ

๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•์€ ๋งˆ์Šคํ„ฐํ–ˆ์œผ๋‹ˆ, ์ด์ œ ๋” ๋ฉ‹์ง„ ๊ธฐ๋Šฅ๋“ค์„ ์•Œ์•„๋ณด์ž! ๐Ÿš€

๐Ÿ”— Intersphinx: ๋‹ค๋ฅธ ๋ฌธ์„œ ์ฐธ์กฐํ•˜๊ธฐ

Python ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์˜ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

# conf.py์— ์ถ”๊ฐ€
extensions = [
    'sphinx.ext.intersphinx',
]

intersphinx_mapping = {
    'python': ('https://docs.python.org/3', None),
    'numpy': ('https://numpy.org/doc/stable/', None),
    'pandas': ('https://pandas.pydata.org/docs/', None),
}

์ด์ œ RST ํŒŒ์ผ์—์„œ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค:

์ด ํ•จ์ˆ˜๋Š” :py:class:`dict`๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

NumPy ๋ฐฐ์—ด์— ๋Œ€ํ•ด์„œ๋Š” :py:class:`numpy.ndarray`๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๐Ÿ“Š Type Hints ์ž๋™ ๋ฌธ์„œํ™”

Python 3.5+์˜ ํƒ€์ž… ํžŒํŠธ๋ฅผ ๋ฌธ์„œ์— ์ž๋™์œผ๋กœ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๐ŸŽจ

# pip install sphinx-autodoc-typehints

# conf.py์— ์ถ”๊ฐ€
extensions = [
    'sphinx_autodoc_typehints',
]

# ํƒ€์ž… ํžŒํŠธ ํ‘œ์‹œ ๋ฐฉ์‹
autodoc_typehints = 'description'  # ๋˜๋Š” 'signature', 'none'
typehints_fully_qualified = False
always_document_param_types = True

์ด์ œ ํƒ€์ž… ํžŒํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฌธ์„œ์— ํฌํ•จ๋œ๋‹ค:

def process_data(
    data: List[Dict[str, Any]], 
    threshold: float = 0.5
) -> Tuple[List[str], int]:
    """๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    
    Args:
        data: ์ฒ˜๋ฆฌํ•  ๋ฐ์ดํ„ฐ ๋ชฉ๋ก
        threshold: ์ž„๊ณ„๊ฐ’
        
    Returns:
        ์ฒ˜๋ฆฌ๋œ ๊ฒฐ๊ณผ์™€ ๊ฐœ์ˆ˜
    """
    pass

๐ŸŽจ ์ปค์Šคํ…€ CSS๋กœ ์Šคํƒ€์ผ๋ง

๋ฌธ์„œ์˜ ์™ธ๊ด€์„ ์™„์ „ํžˆ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ๋‹ค.

/* docs/source/_static/custom.css */

/* ์ฝ”๋“œ ๋ธ”๋ก ์Šคํƒ€์ผ */
.highlight {
    background: #f8f9fa;
    border-left: 4px solid #667eea;
    border-radius: 8px;
    padding: 1em;
}

/* ๊ฒฝ๊ณ  ๋ฐ•์Šค ์ปค์Šคํ„ฐ๋งˆ์ด์ง• */
.admonition.warning {
    background: #fef3c7;
    border-left: 4px solid #f59e0b;
}

/* ๋งํฌ ์ƒ‰์ƒ */
a {
    color: #667eea;
    text-decoration: none;
}

a:hover {
    color: #764ba2;
    text-decoration: underline;
}

/* ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ */
table.docutils {
    border-collapse: collapse;
    width: 100%;
}

table.docutils th {
    background: #667eea;
    color: white;
    padding: 12px;
}

table.docutils td {
    padding: 10px;
    border-bottom: 1px solid #e5e7eb;
}
# conf.py์— ์ถ”๊ฐ€
html_static_path = ['_static']
html_css_files = [
    'custom.css',
]

๐Ÿ“ TODO์™€ ์ฃผ์„ ๊ด€๋ฆฌ

๊ฐœ๋ฐœ ์ค‘์ธ ๊ธฐ๋Šฅ์ด๋‚˜ ๊ฐœ์„  ์‚ฌํ•ญ์„ ๋ฌธ์„œ์— ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.

# conf.py
extensions = [
    'sphinx.ext.todo',
]

todo_include_todos = True  # TODO ํ•ญ๋ชฉ ํ‘œ์‹œ
.. todo::
   
   ์ด ํ•จ์ˆ˜๋Š” ๋น„๋™๊ธฐ ๋ฒ„์ „์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
   ๋‹ค์Œ ๋ฒ„์ „์—์„œ ๊ตฌํ˜„ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

.. deprecated:: 1.5
   
   ์ด ํ•จ์ˆ˜๋Š” ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
   ๋Œ€์‹  :func:`new_function`์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

.. versionadded:: 2.0
   
   ์ƒˆ๋กœ์šด ํŒŒ๋ผ๋ฏธํ„ฐ `timeout`์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ” ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ํ–ฅ์ƒ

# conf.py
html_search_language = 'ko'  # ํ•œ๊ตญ์–ด ๊ฒ€์ƒ‰ ์ง€์›
html_search_options = {
    'type': 'default',
    'minchars': 2,  # ์ตœ์†Œ ๊ฒ€์ƒ‰ ๋ฌธ์ž ์ˆ˜
}
๐Ÿ’ก Pro Tip: ๋ฌธ์„œ ํ’ˆ์งˆ ์ฒดํฌ ๋ฌธ์„œํ™” ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด:

sphinx-build -b coverage docs/source docs/build/coverage

์ด ๋ช…๋ น์–ด๋Š” ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ ํ•จ์ˆ˜์™€ ํด๋ž˜์Šค๋ฅผ ์ฐพ์•„์ค€๋‹ค! ๐Ÿ”

๐Ÿค ํŒ€ ํ˜‘์—…๊ณผ ๋ฌธ์„œํ™” ๋ฌธํ™”

๊ธฐ์ˆ ์ ์ธ ๋ถ€๋ถ„๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ํŒ€ ์ „์ฒด๊ฐ€ ๋ฌธ์„œํ™”๋ฅผ ์Šต๊ด€ํ™”ํ•˜๋Š” ๊ฒŒ ๋” ์ค‘์š”ํ•˜๋‹ค. ๐Ÿ‘ฅ
์žฌ๋Šฅ๋„ท์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•  ๋•Œ๋„, ์ž˜ ์ •๋ฆฌ๋œ ๋ฌธ์„œ๋Š” ์‹ ๋ขฐ๋„๋ฅผ ํฌ๊ฒŒ ๋†’์—ฌ์ค€๋‹ค.

๐Ÿ“‹ ๋ฌธ์„œํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ
  • โ˜‘๏ธ ๋ชจ๋“  public ํ•จ์ˆ˜/ํด๋ž˜์Šค์— docstring ์ž‘์„ฑ
  • โ˜‘๏ธ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋ฐ˜ํ™˜๊ฐ’ ๋ช…์‹œ
  • โ˜‘๏ธ ์˜ˆ์™ธ ์ƒํ™ฉ ๋ฌธ์„œํ™”
  • โ˜‘๏ธ ์‚ฌ์šฉ ์˜ˆ์ œ ํฌํ•จ
  • โ˜‘๏ธ ํƒ€์ž… ํžŒํŠธ ์ถ”๊ฐ€
  • โœ… PR(Pull Request) ์‹œ
  • โ˜‘๏ธ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์˜ ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ
  • โ˜‘๏ธ ๋ณ€๊ฒฝ๋œ API ๋ฌธ์„œ ์ˆ˜์ •
  • โ˜‘๏ธ CHANGELOG ์—…๋ฐ์ดํŠธ
  • โ˜‘๏ธ ๋ฌธ์„œ ๋นŒ๋“œ ์—๋Ÿฌ ํ™•์ธ
  • โœ… ๋ฆด๋ฆฌ์Šค ์ „
  • โ˜‘๏ธ ์ „์ฒด ๋ฌธ์„œ ๋นŒ๋“œ ํ…Œ์ŠคํŠธ
  • โ˜‘๏ธ ๋งํฌ ๊นจ์ง ํ™•์ธ
  • โ˜‘๏ธ ๋ฒ„์ „ ์ •๋ณด ์—…๋ฐ์ดํŠธ
  • โ˜‘๏ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ ์ž‘์„ฑ (breaking changes๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ)

๐ŸŽ“ ์ข‹์€ ๋ฌธ์„œ์˜ ํŠน์ง•

ํŠน์ง• ์„ค๋ช… ์˜ˆ์‹œ
๋ช…ํ™•์„ฑ ๋ˆ„๊ตฌ๋‚˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ "๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค" โŒ
"JSON ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค" โœ…
์™„์ „์„ฑ ๋ชจ๋“  ์ •๋ณด ํฌํ•จ ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฐ˜ํ™˜๊ฐ’, ์˜ˆ์™ธ, ์˜ˆ์ œ ๋ชจ๋‘ ์ž‘์„ฑ
์ผ๊ด€์„ฑ ๊ฐ™์€ ์Šคํƒ€์ผ ์œ ์ง€ ํ”„๋กœ์ ํŠธ ์ „์ฒด์—์„œ ๋™์ผํ•œ docstring ์Šคํƒ€์ผ
์ตœ์‹ ์„ฑ ์ฝ”๋“œ์™€ ๋™๊ธฐํ™” ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ๋ฌธ์„œ๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธ
์˜ˆ์ œ ์‹ค์ œ ์‚ฌ์šฉ๋ฒ• ์ œ์‹œ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์‹ค์šฉ์ ์ธ ์ฝ”๋“œ ์˜ˆ์ œ

๐Ÿšซ ํ”ผํ•ด์•ผ ํ•  ์•ˆํ‹ฐํŒจํ„ด

# โŒ ๋‚˜์œ ์˜ˆ: ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•จ
def calc(a, b):
    """๊ณ„์‚ฐ"""
    return a + b

# โŒ ๋‚˜์œ ์˜ˆ: ์ฝ”๋“œ ๋ฐ˜๋ณต
def add_numbers(x, y):
    """add_numbers๋Š” x์™€ y๋ฅผ ๋”ํ•œ๋‹ค"""
    return x + y

# โŒ ๋‚˜์œ ์˜ˆ: ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ๋งŒ ์„ค๋ช…
def process(data):
    """for ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ๊ฐ ํ•ญ๋ชฉ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค"""
    pass

# โœ… ์ข‹์€ ์˜ˆ: ๋ช…ํ™•ํ•˜๊ณ  ์™„์ „ํ•จ
def calculate_discount(
    original_price: float, 
    discount_rate: float
) -> float:
    """์ƒํ’ˆ์˜ ํ• ์ธ ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    
    ์›๊ฐ€์— ํ• ์ธ์œจ์„ ์ ์šฉํ•˜์—ฌ ์ตœ์ข… ๊ฐ€๊ฒฉ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    ํ• ์ธ์œจ์€ 0๊ณผ 1 ์‚ฌ์ด์˜ ๊ฐ’์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    
    Args:
        original_price: ์ƒํ’ˆ์˜ ์›๊ฐ€ (์–‘์ˆ˜)
        discount_rate: ํ• ์ธ์œจ (0.0 ~ 1.0)
        
    Returns:
        ํ• ์ธ์ด ์ ์šฉ๋œ ์ตœ์ข… ๊ฐ€๊ฒฉ
        
    Raises:
        ValueError: ๊ฐ€๊ฒฉ์ด ์Œ์ˆ˜์ด๊ฑฐ๋‚˜ ํ• ์ธ์œจ์ด ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ๊ฒฝ์šฐ
        
    Examples:
        >>> calculate_discount(10000, 0.2)
        8000.0
        
        >>> calculate_discount(5000, 0.5)
        2500.0
    """
    if original_price < 0:
        raise ValueError("๊ฐ€๊ฒฉ์€ ์–‘์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    if not 0 <= discount_rate <= 1:
        raise ValueError("ํ• ์ธ์œจ์€ 0๊ณผ 1 ์‚ฌ์ด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    
    return original_price * (1 - discount_rate)

๐ŸŽฌ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์™„์ „ํ•œ ์˜ˆ์ œ

์ด์ œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ๐ŸŽฏ
๊ฐ„๋‹จํ•œ REST API ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  ๋ฌธ์„œํ™”ํ•ด๋ณด๊ฒ ๋‹ค.

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

api-client/
โ”œโ”€โ”€ apiclient/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ client.py
โ”‚   โ”œโ”€โ”€ exceptions.py
โ”‚   โ””โ”€โ”€ models.py
โ”œโ”€โ”€ docs/
โ”‚   โ”œโ”€โ”€ source/
โ”‚   โ”‚   โ”œโ”€โ”€ conf.py
โ”‚   โ”‚   โ”œโ”€โ”€ index.rst
โ”‚   โ”‚   โ”œโ”€โ”€ quickstart.rst
โ”‚   โ”‚   โ”œโ”€โ”€ api.rst
โ”‚   โ”‚   โ””โ”€โ”€ examples.rst
โ”‚   โ””โ”€โ”€ Makefile
โ”œโ”€โ”€ tests/
โ”‚   โ””โ”€โ”€ test_client.py
โ”œโ”€โ”€ setup.py
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ README.md

๐Ÿ’ป ์ฝ”๋“œ ๊ตฌํ˜„

# apiclient/exceptions.py

class APIClientError(Exception):
    """API ํด๋ผ์ด์–ธํŠธ์˜ ๊ธฐ๋ณธ ์˜ˆ์™ธ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
    
    ๋ชจ๋“  ์ปค์Šคํ…€ ์˜ˆ์™ธ๋Š” ์ด ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค.
    """
    pass

class AuthenticationError(APIClientError):
    """์ธ์ฆ ์‹คํŒจ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค.
    
    API ํ‚ค๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    
    Attributes:
        message: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
        status_code: HTTP ์ƒํƒœ ์ฝ”๋“œ
    """
    
    def __init__(self, message: str, status_code: int = 401):
        self.message = message
        self.status_code = status_code
        super().__init__(self.message)

class RateLimitError(APIClientError):
    """API ํ˜ธ์ถœ ์ œํ•œ์„ ์ดˆ๊ณผํ–ˆ์„ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค.
    
    Attributes:
        retry_after: ์žฌ์‹œ๋„ ๊ฐ€๋Šฅํ•œ ์‹œ๊ฐ„ (์ดˆ)
    """
    
    def __init__(self, retry_after: int):
        self.retry_after = retry_after
        super().__init__(f"Rate limit exceeded. Retry after {retry_after} seconds")
# apiclient/models.py

from dataclasses import dataclass
from typing import Optional, Dict, Any
from datetime import datetime

@dataclass
class APIResponse:
    """API ์‘๋‹ต์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
    
    ๋ชจ๋“  API ํ˜ธ์ถœ์˜ ๊ฒฐ๊ณผ๋Š” ์ด ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
    
    Attributes:
        status_code: HTTP ์ƒํƒœ ์ฝ”๋“œ
        data: ์‘๋‹ต ๋ฐ์ดํ„ฐ (JSON)
        headers: ์‘๋‹ต ํ—ค๋”
        timestamp: ์‘๋‹ต ์‹œ๊ฐ„
        
    Examples:
        >>> response = APIResponse(200, {"result": "success"}, {})
        >>> response.is_success()
        True
    """
    
    status_code: int
    data: Dict[str, Any]
    headers: Dict[str, str]
    timestamp: datetime = None
    
    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.now()
    
    def is_success(self) -> bool:
        """์‘๋‹ต์ด ์„ฑ๊ณต์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
        
        Returns:
            200๋ฒˆ๋Œ€ ์ƒํƒœ ์ฝ”๋“œ๋ฉด True, ์•„๋‹ˆ๋ฉด False
        """
        return 200 <= self.status_code < 300
    
    def get(self, key: str, default: Any = None) -> Any:
        """์‘๋‹ต ๋ฐ์ดํ„ฐ์—์„œ ํŠน์ • ํ‚ค์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        
        Args:
            key: ๊ฐ€์ ธ์˜ฌ ํ‚ค
            default: ํ‚ค๊ฐ€ ์—†์„ ๋•Œ ๋ฐ˜ํ™˜ํ•  ๊ธฐ๋ณธ๊ฐ’
            
        Returns:
            ํ‚ค์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ’ ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’
        """
        return self.data.get(key, default)
# apiclient/client.py

import requests
from typing import Optional, Dict, Any
from .models import APIResponse
from .exceptions import AuthenticationError, RateLimitError, APIClientError

class APIClient:
    """REST API์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•œ ํด๋ผ์ด์–ธํŠธ์ž…๋‹ˆ๋‹ค.
    
    ์ด ํด๋ž˜์Šค๋Š” HTTP ์š”์ฒญ์„ ์ถ”์ƒํ™”ํ•˜๊ณ , ์ธ์ฆ, ์—๋Ÿฌ ์ฒ˜๋ฆฌ,
    ์žฌ์‹œ๋„ ๋กœ์ง ๋“ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
    
    Attributes:
        base_url: API์˜ ๊ธฐ๋ณธ URL
        api_key: ์ธ์ฆ์— ์‚ฌ์šฉํ•  API ํ‚ค
        timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
        
    Examples:
        >>> client = APIClient("https://api.example.com", "your-api-key")
        >>> response = client.get("/users/123")
        >>> print(response.data)
        {'id': 123, 'name': 'John Doe'}
        
    Note:
        ์ด ํด๋ผ์ด์–ธํŠธ๋Š” ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
        ์žฌ์‹œ๋„๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ณ„๋„๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    """
    
    def __init__(
        self, 
        base_url: str, 
        api_key: str,
        timeout: int = 30
    ):
        """APIClient ์ธ์Šคํ„ด์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            base_url: API์˜ ๊ธฐ๋ณธ URL (์˜ˆ: "https://api.example.com")
            api_key: ์ธ์ฆ์— ์‚ฌ์šฉํ•  API ํ‚ค
            timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ). ๊ธฐ๋ณธ๊ฐ’์€ 30.
            
        Raises:
            ValueError: base_url์ด๋‚˜ api_key๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ
        """
        if not base_url or not api_key:
            raise ValueError("base_url and api_key are required")
        
        self.base_url = base_url.rstrip('/')
        self.api_key = api_key
        self.timeout = timeout
        self._session = requests.Session()
        self._session.headers.update({
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        })
    
    def get(
        self, 
        endpoint: str, 
        params: Optional[Dict[str, Any]] = None
    ) -> APIResponse:
        """GET ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            endpoint: API ์—”๋“œํฌ์ธํŠธ (์˜ˆ: "/users/123")
            params: ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ (์„ ํƒ์‚ฌํ•ญ)
            
        Returns:
            API ์‘๋‹ต์„ ๋‹ด์€ APIResponse ๊ฐ์ฒด
            
        Raises:
            AuthenticationError: ์ธ์ฆ ์‹คํŒจ ์‹œ
            RateLimitError: API ํ˜ธ์ถœ ์ œํ•œ ์ดˆ๊ณผ ์‹œ
            APIClientError: ๊ธฐํƒ€ API ์—๋Ÿฌ
            
        Examples:
            >>> response = client.get("/users", params={"page": 1})
            >>> users = response.data['users']
        """
        return self._request('GET', endpoint, params=params)
    
    def post(
        self, 
        endpoint: str, 
        data: Optional[Dict[str, Any]] = None
    ) -> APIResponse:
        """POST ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
        
        Args:
            endpoint: API ์—”๋“œํฌ์ธํŠธ
            data: ์š”์ฒญ ๋ณธ๋ฌธ ๋ฐ์ดํ„ฐ
            
        Returns:
            API ์‘๋‹ต์„ ๋‹ด์€ APIResponse ๊ฐ์ฒด
            
        Raises:
            AuthenticationError: ์ธ์ฆ ์‹คํŒจ ์‹œ
            RateLimitError: API ํ˜ธ์ถœ ์ œํ•œ ์ดˆ๊ณผ ์‹œ
            APIClientError: ๊ธฐํƒ€ API ์—๋Ÿฌ
        """
        return self._request('POST', endpoint, json=data)
    
    def _request(
        self, 
        method: str, 
        endpoint: str, 
        **kwargs
    ) -> APIResponse:
        """์‹ค์ œ HTTP ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
        
        Args:
            method: HTTP ๋ฉ”์„œ๋“œ (GET, POST ๋“ฑ)
            endpoint: API ์—”๋“œํฌ์ธํŠธ
            **kwargs: requests ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ „๋‹ฌํ•  ์ถ”๊ฐ€ ์ธ์ž
            
        Returns:
            API ์‘๋‹ต
            
        Raises:
            AuthenticationError: 401 ์‘๋‹ต ์‹œ
            RateLimitError: 429 ์‘๋‹ต ์‹œ
            APIClientError: ๊ธฐํƒ€ ์—๋Ÿฌ
        """
        url = f"{self.base_url}{endpoint}"
        
        try:
            response = self._session.request(
                method, 
                url, 
                timeout=self.timeout,
                **kwargs
            )
            
            if response.status_code == 401:
                raise AuthenticationError("Invalid API key")
            
            if response.status_code == 429:
                retry_after = int(response.headers.get('Retry-After', 60))
                raise RateLimitError(retry_after)
            
            return APIResponse(
                status_code=response.status_code,
                data=response.json() if response.content else {},
                headers=dict(response.headers)
            )
            
        except requests.exceptions.Timeout:
            raise APIClientError(f"Request timeout after {self.timeout} seconds")
        except requests.exceptions.RequestException as e:
            raise APIClientError(f"Request failed: {str(e)}")
    
    def close(self):
        """์„ธ์…˜์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
        
        ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ด ๋๋‚˜๋ฉด ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
        
        Examples:
            >>> client = APIClient(base_url, api_key)
            >>> try:
            ...     response = client.get("/data")
            ... finally:
            ...     client.close()
        """
        self._session.close()
    
    def __enter__(self):
        """์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € ์ง„์ž…."""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € ์ข…๋ฃŒ."""
        self.close()

๐Ÿ“– ๋ฌธ์„œ ์ž‘์„ฑ

.. quickstart.rst

๋น ๋ฅธ ์‹œ์ž‘
=========

์„ค์น˜
----

pip๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

.. code-block:: bash

   pip install api-client

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•
-----------

API ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค:

.. code-block:: python

   from apiclient import APIClient
   
   # ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
   client = APIClient(
       base_url="https://api.example.com",
       api_key="your-api-key-here"
   )
   
   # GET ์š”์ฒญ
   response = client.get("/users/123")
   print(response.data)
   
   # POST ์š”์ฒญ
   new_user = {
       "name": "John Doe",
       "email": "john@example.com"
   }
   response = client.post("/users", data=new_user)
   
   # ์„ธ์…˜ ์ข…๋ฃŒ
   client.close()

์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ € ์‚ฌ์šฉ
--------------------

์ž๋™์œผ๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•˜๋ ค๋ฉด ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

.. code-block:: python

   with APIClient(base_url, api_key) as client:
       response = client.get("/data")
       # ์ž๋™์œผ๋กœ close() ํ˜ธ์ถœ๋จ

์—๋Ÿฌ ์ฒ˜๋ฆฌ
---------

๋‹ค์–‘ํ•œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

.. code-block:: python

   from apiclient import APIClient
   from apiclient.exceptions import (
       AuthenticationError,
       RateLimitError,
       APIClientError
   )
   
   try:
       with APIClient(base_url, api_key) as client:
           response = client.get("/data")
           
   except AuthenticationError:
       print("API ํ‚ค๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค")
       
   except RateLimitError as e:
       print(f"{e.retry_after}์ดˆ ํ›„์— ์žฌ์‹œ๋„ํ•˜์„ธ์š”")
       
   except APIClientError as e:
       print(f"API ์—๋Ÿฌ: {e}")

๋‹ค์Œ ๋‹จ๊ณ„
---------

* :doc:`api` - ์ „์ฒด API ๋ ˆํผ๋Ÿฐ์Šค
* :doc:`examples` - ๋” ๋งŽ์€ ์˜ˆ์ œ

์ด๋ ‡๊ฒŒ ์™„์„ฑ๋œ ํ”„๋กœ์ ํŠธ๋Š” ์ „๋ฌธ์ ์ด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์šด ๋ฌธ์„œ๋ฅผ ๊ฐ–์ถ”๊ฒŒ ๋œ๋‹ค! ๐ŸŽ‰


๐ŸŽ“ ๋งˆ์น˜๋ฉฐ

์—ฌ๊ธฐ๊นŒ์ง€ Python ๋ฌธ์„œํ™”์™€ Sphinx๋ฅผ ํ™œ์šฉํ•œ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ์— ๋Œ€ํ•ด ์•Œ์•„๋ดค๋‹ค. ๐ŸŽŠ

์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•œ ๋ฒˆ ์„ค์ •ํ•ด๋‘๋ฉด ์ •๋ง ํŽธํ•˜๋‹ค.
์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ docstring๋งŒ ์ž˜ ์ž‘์„ฑํ•˜๋ฉด, ๋‚˜๋จธ์ง€๋Š” Sphinx๊ฐ€ ์•Œ์•„์„œ ํ•ด์ค€๋‹ค!

ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•  ๋•Œ,
์ž˜ ์ •๋ฆฌ๋œ ๋ฌธ์„œ๋Š” ๋‹น์‹ ์˜ ์ „๋ฌธ์„ฑ์„ ๋ณด์—ฌ์ฃผ๋Š” ์ตœ๊ณ ์˜ ๋ช…ํ•จ์ด ๋œ๋‹ค. ๐Ÿ’ผ

๋ฌธ์„œํ™”๋Š” ๋‹จ์ˆœํžˆ ๊ท€์ฐฎ์€ ์ž‘์—…์ด ์•„๋‹ˆ๋ผ,
๋ฏธ๋ž˜์˜ ๋‹น์‹ ๊ณผ ํŒ€์›๋“ค์—๊ฒŒ ์ฃผ๋Š” ์„ ๋ฌผ์ด๋‹ค. ๐ŸŽ

์˜ค๋Š˜๋ถ€ํ„ฐ ๋‹น์žฅ ์‹œ์ž‘ํ•ด๋ณด์ž!
์ž‘์€ ํ”„๋กœ์ ํŠธ๋ผ๋„ ๋ฌธ์„œํ™” ์Šต๊ด€์„ ๋“ค์ด๋ฉด,
๋‚˜์ค‘์— ํฐ ํ”„๋กœ์ ํŠธ์—์„œ ์—„์ฒญ๋‚œ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿš€ ํ•ต์‹ฌ ์š”์•ฝ 1๏ธโƒฃ Docstring์€ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ž‘์„ฑํ•˜์ž (๋‚˜์ค‘์œผ๋กœ ๋ฏธ๋ฃจ์ง€ ๋ง๊ณ !)
2๏ธโƒฃ Google Style์ด ๊ฐ€์žฅ ์ฝ๊ธฐ ์‰ฝ๊ณ  ์ž‘์„ฑํ•˜๊ธฐ ํŽธํ•˜๋‹ค
3๏ธโƒฃ Sphinx๋Š” ์„ค์ •๋งŒ ์ž˜ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋‹ค ํ•ด์ค€๋‹ค
4๏ธโƒฃ Read the Docs๋กœ ๋ฌด๋ฃŒ ํ˜ธ์ŠคํŒ…ํ•˜์ž
5๏ธโƒฃ ํƒ€์ž… ํžŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌธ์„œ๊ฐ€ ๋” ๋ช…ํ™•ํ•ด์ง„๋‹ค
6๏ธโƒฃ ์˜ˆ์ œ ์ฝ”๋“œ๋Š” ํ•„์ˆ˜! ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์žฅ ๋จผ์ € ๋ณด๋Š” ๋ถ€๋ถ„์ด๋‹ค
7๏ธโƒฃ ๋ฌธ์„œํ™”๋Š” ํŒ€ ๋ฌธํ™”๋‹ค. ๋ชจ๋‘๊ฐ€ ํ•จ๊ป˜ ์ฐธ์—ฌํ•ด์•ผ ํ•œ๋‹ค

์ด์ œ ๋‹น์‹ ๋„ ๋ฌธ์„œํ™” ๋งˆ์Šคํ„ฐ๋‹ค! ๐Ÿ†
๋ฉ‹์ง„ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์„ธ์ƒ๊ณผ ๊ณต์œ ํ•ด๋ณด์ž!

Happy Documenting! ๐Ÿ“šโœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.