Ver3.0 ๐ Python ๋ฌธ์ํ์ Sphinx๋ก API ๋ฌธ์ ์๋ ์์ฑํ๊ธฐ

๐ Python ๋ฌธ์ํ์ Sphinx๋ก API ๋ฌธ์ ์๋ ์์ฑํ๊ธฐ
๊ฐ๋ฐ์์ ์์ํ ์์ , ๋ฌธ์ํ๋ฅผ ์๋์ผ๋ก ํด๊ฒฐํ๋ ๋ง๋ฒ ๊ฐ์ ์ด์ผ๊ธฐ
๐ ์ ๋ฌธ์ํ๋ ํญ์ ๋ฏธ๋ฃจ๊ฒ ๋ ๊น?
์์งํ ๋งํด๋ณด์. ์ฝ๋ ์ง๋ ๊ฑด ์ฌ๋ฐ๋๋ฐ ๋ฌธ์ ์ฐ๋ ๊ฑด ์ ๋ง... ์ง๋ฃจํ์ง ์๋? ๐
๋๋ ๊ทธ๋ฌ๋ค. ๋ฉ์ง API๋ฅผ ๋ง๋ค์ด๋๊ณ ๋์ค์ "์ด๊ฑฐ ์ด๋ป๊ฒ ์ฐ๋ ๊ฑฐ์์ง?"๋ผ๋ฉฐ ๋ด ์ฝ๋๋ฅผ ๋ค์ ๋ฏ์ด๋ณด๋ ๊ฒฝํ, ํ ๋ฒ์ฏค์ ์์ ๊ฑฐ๋ค.
๋ ์๊ธด ๊ฑด ํ์์ด "์ด ํจ์ ํ๋ผ๋ฏธํฐ๊ฐ ๋ญ์์?"๋ผ๊ณ ๋ฌผ์ด๋ณผ ๋๋ค. ๋ถ๋ช
3๊ฐ์ ์ ์ ๋ด๊ฐ ๋ง๋ ๊ฑด๋ฐ ๊ธฐ์ต์ด ์ ๋์ ์ฝ๋๋ฅผ ๋ค์ ์ฝ์ด์ผ ํ๋ ์ํฉ. ๐คฆโโ๏ธ
์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ ๋ฐ๋ก ์๋ ๋ฌธ์ํ ๋๊ตฌ๋ค. ๊ทธ์ค์์๋ Python ์ํ๊ณ์์ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ Sphinx๋ ์ ๋ง ๊ฐ๋ ฅํ ๋๊ตฌ๋ค.
๐ฏ Sphinx๊ฐ ๋ญ๊ธธ๋?
Sphinx๋ ์๋ Python ๊ณต์ ๋ฌธ์๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๊ฐ๋ฐ๋ ๋๊ตฌ๋ค. ๐
์ง๊ธ์ Python๋ฟ๋ง ์๋๋ผ C, C++, JavaScript ๋ฑ ๋ค์ํ ์ธ์ด์ ๋ฌธ์ํ์๋ ์ฌ์ฉ๋๊ณ ์๋ค.
Sphinx์ ํต์ฌ ๊ธฐ๋ฅ์ ์ด๋ ๋ค:
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
๋๋ถ๋ถ ๊ธฐ๋ณธ๊ฐ(์ํฐ)์ผ๋ก ์งํํด๋ ๋์ง๋ง, ๋ช ๊ฐ์ง๋ ์ฃผ์ํด์ ๋ตํ์:
- "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)
๊ฐ๋ ์ฑ์ด ์ข๊ณ , ์์ฑํ๊ธฐ๋ ํธํ๋ฉฐ, ๋ง์ ์คํ์์ค ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ค.
ํ์ง๋ง ํ์ด๋ ํ๋ก์ ํธ์ ์ด๋ฏธ ์ ํด์ง ์คํ์ผ์ด ์๋ค๋ฉด ๊ทธ๊ฑธ ๋ฐ๋ฅด๋ ๊ฒ ์ต์ ์ด๋ค.
์ผ๊ด์ฑ์ด ๊ฐ์ฅ ์ค์ํ๋๊น!
๐๏ธ ํด๋์ค ๋ฌธ์ํ ์์
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)
โ๏ธ 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
์ด๊ฒ ์์ผ๋ฉด 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
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`
๐๏ธ ์ค์ ํ๋ก์ ํธ ๊ตฌ์กฐ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ํ๋ก์ ํธ์ ์ด๋ป๊ฒ ์ ์ฉํ๋์ง ๋ณด์. ๐ ๏ธ
์ ํ์ ์ธ 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 ํ์ผ์ด ์์ฑ๋๋ค.
๋ธ๋ผ์ฐ์ ๋ก ์ด์ด๋ณด๋ฉด ๋ฉ์ง ๋ฌธ์๊ฐ ์์ฑ๋์ด ์์ ๊ฑฐ๋ค! ๐
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๋ก ํ ๋ง ์ค์น |
๋ฐ๋ผ์ ๋ชจ๋ ๋ ๋ฒจ์์ ๋ฌด๊ฑฐ์ด ์์ (DB ์ฐ๊ฒฐ, ํ์ผ ์ฝ๊ธฐ ๋ฑ)์ ํ๋ฉด ๋น๋๊ฐ ๋๋ ค์ง๊ฑฐ๋ ์คํจํ ์ ์๋ค.
ํด๊ฒฐ์ฑ : if __name__ == '__main__': ๋ธ๋ก ์ฌ์ฉํ๊ฑฐ๋,
conf.py์์ autodoc_mock_imports๋ก ํน์ ๋ชจ๋์ mock ์ฒ๋ฆฌ
๐ ๋ฌธ์ ๋ฐฐํฌํ๊ธฐ
๋ฌธ์๋ฅผ ๋ง๋ค์์ผ๋ฉด ์ด์ ์ธ์์ ๊ณต๊ฐํ ์ฐจ๋ก๋ค! ๐
์ฌ๋ฌ ๊ฐ์ง ๋ฐฐํฌ ๋ฐฉ๋ฒ์ด ์๋๋ฐ, ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๊ฒ๋ค์ ์๊ฐํ๋ค.
๐ Read the Docs
Read the Docs๋ ๋ฌด๋ฃ๋ก ๋ฌธ์๋ฅผ ํธ์คํ
ํด์ฃผ๋ ์ต๊ณ ์ ํ๋ซํผ์ด๋ค. ๐
GitHub์ ์ฐ๋ํ๋ฉด ์๋์ผ๋ก ๋น๋๋๊ณ ๋ฐฐํฌ๋๋ค.
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/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 ์์ ํ์ธ ๊ฐ๋ฅ!
๐ฏ ๊ณ ๊ธ ๊ธฐ๋ฅ๊ณผ ํ
๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ ๋ง์คํฐํ์ผ๋, ์ด์ ๋ ๋ฉ์ง ๊ธฐ๋ฅ๋ค์ ์์๋ณด์! ๐
๐ 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, # ์ต์ ๊ฒ์ ๋ฌธ์ ์
}
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๊ฐ ์์์ ํด์ค๋ค!
ํนํ ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ ๋,
์ ์ ๋ฆฌ๋ ๋ฌธ์๋ ๋น์ ์ ์ ๋ฌธ์ฑ์ ๋ณด์ฌ์ฃผ๋ ์ต๊ณ ์ ๋ช
ํจ์ด ๋๋ค. ๐ผ
๋ฌธ์ํ๋ ๋จ์ํ ๊ท์ฐฎ์ ์์
์ด ์๋๋ผ,
๋ฏธ๋์ ๋น์ ๊ณผ ํ์๋ค์๊ฒ ์ฃผ๋ ์ ๋ฌผ์ด๋ค. ๐
์ค๋๋ถํฐ ๋น์ฅ ์์ํด๋ณด์!
์์ ํ๋ก์ ํธ๋ผ๋ ๋ฌธ์ํ ์ต๊ด์ ๋ค์ด๋ฉด,
๋์ค์ ํฐ ํ๋ก์ ํธ์์ ์์ฒญ๋ ์๊ฐ์ ์ ์ฝํ ์ ์๋ค.
2๏ธโฃ Google Style์ด ๊ฐ์ฅ ์ฝ๊ธฐ ์ฝ๊ณ ์์ฑํ๊ธฐ ํธํ๋ค
3๏ธโฃ Sphinx๋ ์ค์ ๋ง ์ํ๋ฉด ์๋์ผ๋ก ๋ค ํด์ค๋ค
4๏ธโฃ Read the Docs๋ก ๋ฌด๋ฃ ํธ์คํ ํ์
5๏ธโฃ ํ์ ํํธ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฌธ์๊ฐ ๋ ๋ช ํํด์ง๋ค
6๏ธโฃ ์์ ์ฝ๋๋ ํ์! ์ฌ์ฉ์๊ฐ ๊ฐ์ฅ ๋จผ์ ๋ณด๋ ๋ถ๋ถ์ด๋ค
7๏ธโฃ ๋ฌธ์ํ๋ ํ ๋ฌธํ๋ค. ๋ชจ๋๊ฐ ํจ๊ป ์ฐธ์ฌํด์ผ ํ๋ค
์ด์ ๋น์ ๋ ๋ฌธ์ํ ๋ง์คํฐ๋ค! ๐
๋ฉ์ง ๋ฌธ์๋ฅผ ๋ง๋ค์ด์ ์ธ์๊ณผ ๊ณต์ ํด๋ณด์!
Happy Documenting! ๐โจ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.