Python Documentation Guide¶
Google Style + Sphinx + Type Hints für PySide6 Projekte
Inhaltsverzeichnis¶
- Setup & Installation
- Google Style Docstrings
- Type Hints Best Practices
- PySide6-Spezifische Dokumentation
- Sphinx Konfiguration
- Auto-Documentation Setup
- Read the Docs Integration
- PyCharm Integration
- Best Practices
Setup & Installation¶
Projekt-Struktur¶
myproject/
├── src/
│ └── myapp/
│ ├── __init__.py
│ ├── models/
│ ├── views/
│ └── controllers/
├── docs/
│ ├── source/
│ │ ├── conf.py
│ │ ├── index.rst
│ │ └── api/
│ ├── build/
│ └── Makefile
├── tests/
├── pyproject.toml
└── README.md
Installation¶
pyproject.toml:
[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
"PySide6>=6.5.0",
]
[project.optional-dependencies]
docs = [
"sphinx>=7.0.0",
"sphinx-rtd-theme>=2.0.0",
"sphinx-autodoc-typehints>=1.24.0",
"myst-parser>=2.0.0", # Optional: Markdown support
]
dev = [
"mypy>=1.5.0",
"black>=23.0.0",
"ruff>=0.1.0",
]
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Installation:
# Projekt installieren
pip install -e .
# Docs dependencies
pip install -e ".[docs]"
# Dev dependencies
pip install -e ".[dev]"
Sphinx Initialisierung¶
# Docs-Verzeichnis erstellen
mkdir docs
cd docs
# Sphinx quickstart
sphinx-quickstart
# Antworten:
# > Separate source and build directories (y/n) [n]: y
# > Project name: My App
# > Author name(s): Your Name
# > Project release []: 0.1.0
# > Project language [en]: en
Resultierende Struktur:
docs/
├── source/
│ ├── conf.py
│ ├── index.rst
│ ├── _static/
│ └── _templates/
├── build/
└── Makefile
Google Style Docstrings¶
Modul-Docstring¶
# src/myapp/models/data_model.py
"""
Data Model Module.
This module contains the core data model classes for the application.
It provides the Model component of the MVP architecture and handles
all data persistence and business logic.
Example:
Basic usage of the DataModel::
from myapp.models.data_model import DataModel
model = DataModel()
model.add_item("Item 1")
model.save("data.json")
Todo:
* Add support for database backends
* Implement data validation
* Add undo/redo functionality
.. _Google Python Style Guide:
https://google.github.io/styleguide/pyguide.html
"""
from typing import List, Optional, Dict, Any
from pathlib import Path
from PySide6.QtCore import QObject, Signal
Klassen-Docstring¶
class DataModel(QObject):
"""
Core data model for application data.
This class manages the application's data and provides a clean interface
for data manipulation. It follows Qt's signal/slot mechanism for
notifying views of data changes.
The model maintains a list of items and tracks modification state.
Data can be persisted to JSON files.
Attributes:
items (List[str]): List of data items (read-only property).
modified (bool): True if data has been modified since last save.
file_path (Optional[Path]): Path to currently loaded file.
Signals:
data_changed(list): Emitted when items are added, removed, or modified.
Args:
list: The updated list of items.
error_occurred(str): Emitted when an error occurs.
Args:
str: Error message describing what went wrong.
file_loaded(Path): Emitted when a file is successfully loaded.
Args:
Path: Path to the loaded file.
Example:
Create a model and add items::
model = DataModel()
model.data_changed.connect(lambda items: print(f"Items: {items}"))
model.add_item("First Item")
model.save(Path("data.json"))
Note:
This class is thread-safe for signal emissions but not for
direct data access. Use signals for cross-thread communication.
See Also:
:class:`DataView`: The view component that displays this model.
:class:`DataController`: The controller that coordinates model and view.
"""
# Type annotations for signals (for documentation)
data_changed: Signal = Signal(list)
error_occurred: Signal = Signal(str)
file_loaded: Signal = Signal(Path)
def __init__(self, parent: Optional[QObject] = None):
"""
Initialize the data model.
Args:
parent: Optional parent QObject for memory management.
If provided, this object will be deleted when parent is deleted.
Example:
>>> model = DataModel()
>>> model.items
[]
"""
super().__init__(parent)
self._items: List[str] = []
self._modified: bool = False
self._file_path: Optional[Path] = None
Methoden-Docstring: Standard¶
def add_item(self, item: str) -> None:
"""
Add an item to the model.
The item is appended to the internal list and the data_changed
signal is emitted. Marks the model as modified.
Args:
item: The item to add. Must not be empty or already exist.
Raises:
ValueError: If item is empty string.
ValueError: If item already exists in the list.
Example:
>>> model = DataModel()
>>> model.add_item("Task 1")
>>> model.items
['Task 1']
Note:
This method is thread-safe via Qt's signal mechanism.
"""
if not item:
raise ValueError("Item cannot be empty")
if item in self._items:
raise ValueError(f"Item '{item}' already exists")
self._items.append(item)
self._modified = True
self.data_changed.emit(self._items.copy())
Methoden-Docstring: Mit Type Hints¶
def load(self, file_path: Path) -> bool:
"""
Load data from a JSON file.
Loads items from the specified JSON file and replaces current data.
Emits file_loaded signal on success or error_occurred on failure.
Args:
file_path: Path to the JSON file to load.
Returns:
True if loading was successful, False otherwise.
Raises:
FileNotFoundError: If the file does not exist.
json.JSONDecodeError: If the file contains invalid JSON.
PermissionError: If the file cannot be read due to permissions.
Example:
Load data from a file::
model = DataModel()
try:
success = model.load(Path("data.json"))
if success:
print(f"Loaded {len(model.items)} items")
except FileNotFoundError:
print("File not found")
Warning:
This method will discard any unsaved changes. Make sure to
call :meth:`save` before loading if needed.
See Also:
:meth:`save`: Save data to a file.
"""
try:
with open(file_path, 'r') as f:
data = json.load(f)
self._items = data.get('items', [])
self._file_path = file_path
self._modified = False
self.file_loaded.emit(file_path)
self.data_changed.emit(self._items.copy())
return True
except Exception as e:
self.error_occurred.emit(str(e))
return False
Property-Docstring¶
@property
def items(self) -> List[str]:
"""
Get a copy of all items.
Returns:
A copy of the items list. Modifications to this list
will not affect the internal data.
Example:
>>> model = DataModel()
>>> model.add_item("Item 1")
>>> items = model.items
>>> items.append("Item 2") # Does not affect model
>>> model.items
['Item 1']
Note:
Returns a copy to prevent external modification of internal state.
"""
return self._items.copy()
@property
def modified(self) -> bool:
"""
Check if data has been modified since last save.
Returns:
True if data has been modified, False otherwise.
Example:
>>> model = DataModel()
>>> model.modified
False
>>> model.add_item("Item")
>>> model.modified
True
"""
return self._modified
Komplexe Beispiele¶
def filter_items(
self,
predicate: Callable[[str], bool],
case_sensitive: bool = True
) -> List[str]:
"""
Filter items using a predicate function.
Args:
predicate: Function that takes an item and returns True to include it.
case_sensitive: If False, convert items to lowercase before filtering.
Defaults to True.
Returns:
List of items that match the predicate.
Example:
Filter items containing 'test'::
model.add_item("Test 1")
model.add_item("Test 2")
model.add_item("Other")
# Case sensitive
result = model.filter_items(lambda x: 'Test' in x)
# result: ['Test 1', 'Test 2']
# Case insensitive
result = model.filter_items(
lambda x: 'test' in x,
case_sensitive=False
)
# result: ['Test 1', 'Test 2']
Note:
This method does not modify the original list.
"""
items = self._items if case_sensitive else [i.lower() for i in self._items]
return [item for item in items if predicate(item)]
Type Hints Best Practices¶
Import Standards¶
from typing import (
List, # List[int], List[str]
Dict, # Dict[str, int]
Set, # Set[str]
Tuple, # Tuple[int, str]
Optional, # Optional[str] = Union[str, None]
Union, # Union[int, str]
Any, # Any type (use sparingly!)
Callable, # Callable[[int, str], bool]
TypeVar, # Generic types
Generic, # Generic base class
Protocol, # Structural subtyping
cast, # Type casting
overload, # Function overloading
)
from pathlib import Path
from PySide6.QtCore import QObject, Signal
Type Aliases¶
"""Type aliases for better readability."""
# Simple aliases
ItemID = int
ItemName = str
ItemDict = Dict[ItemID, ItemName]
# Complex types
CallbackType = Callable[[str, int], bool]
SignalType = Signal
# Generic aliases
T = TypeVar('T')
ItemList = List[T]
# Usage
def process_items(items: ItemList[str]) -> ItemDict:
"""Process items and return mapping."""
return {idx: name for idx, name in enumerate(items)}
PySide6 Type Hints¶
from PySide6.QtCore import QObject, Signal, Slot, QTimer
from PySide6.QtWidgets import QWidget, QPushButton, QVBoxLayout
from typing import Optional
class MyWidget(QWidget):
"""Custom widget with proper type hints."""
# Signal with type annotation (for documentation)
value_changed: Signal = Signal(int)
def __init__(self, parent: Optional[QWidget] = None):
"""
Initialize widget.
Args:
parent: Parent widget for Qt ownership.
"""
super().__init__(parent)
self._value: int = 0
self._button: QPushButton = QPushButton("Click")
self._timer: Optional[QTimer] = None
@Slot(int)
def set_value(self, value: int) -> None:
"""
Set the widget value.
Args:
value: New value to set.
"""
self._value = value
self.value_changed.emit(value)
def get_value(self) -> int:
"""
Get current value.
Returns:
Current widget value.
"""
return self._value
Generic Types¶
from typing import TypeVar, Generic, List
T = TypeVar('T')
class DataContainer(Generic[T]):
"""
Generic container for any data type.
Type Parameters:
T: The type of items stored in this container.
Example:
>>> container = DataContainer[str]()
>>> container.add("item")
>>> container.items
['item']
"""
def __init__(self):
self._items: List[T] = []
def add(self, item: T) -> None:
"""
Add an item to the container.
Args:
item: Item to add.
"""
self._items.append(item)
@property
def items(self) -> List[T]:
"""Get all items."""
return self._items.copy()
Union Types (Python 3.10+)¶
# Old style
from typing import Union, Optional
def process(value: Union[int, str]) -> Optional[bool]:
"""Process value."""
pass
# New style (Python 3.10+)
def process(value: int | str) -> bool | None:
"""Process value."""
pass
Literal Types¶
from typing import Literal
ConnectionType = Literal['auto', 'direct', 'queued', 'blocking']
def connect_signal(
signal: Signal,
slot: Callable,
connection_type: ConnectionType = 'auto'
) -> None:
"""
Connect signal to slot.
Args:
signal: Signal to connect.
slot: Slot function to call.
connection_type: Type of connection. One of:
- 'auto': Automatic (default)
- 'direct': Direct connection
- 'queued': Queued connection
- 'blocking': Blocking queued connection
"""
pass
PySide6-Spezifische Dokumentation¶
Signals Dokumentieren¶
class Worker(QObject):
"""
Background worker thread.
This worker performs long-running operations in a separate thread
and reports progress via signals.
Signals:
progress(int): Emitted periodically during work.
Args:
int: Progress percentage (0-100).
finished(dict): Emitted when work is complete.
Args:
dict: Results dictionary with keys:
- 'status' (str): 'success' or 'error'
- 'data' (Any): Result data
- 'duration' (float): Execution time in seconds
error(str, Exception): Emitted when an error occurs.
Args:
str: Error message.
Exception: The exception that occurred.
Example:
Setup and run a worker::
worker = Worker()
thread = QThread()
worker.moveToThread(thread)
worker.finished.connect(thread.quit)
worker.progress.connect(lambda p: print(f"Progress: {p}%"))
thread.started.connect(worker.do_work)
thread.start()
"""
progress: Signal = Signal(int)
finished: Signal = Signal(dict)
error: Signal = Signal(str, Exception)
Slots Dokumentieren¶
class DataView(QWidget):
"""View for displaying data."""
@Slot(list)
def update_display(self, items: List[str]) -> None:
"""
Update the display with new items.
This slot is connected to the model's data_changed signal
and updates the UI accordingly.
Args:
items: List of items to display.
Note:
This method runs in the GUI thread and should complete quickly.
For expensive operations, use a worker thread.
See Also:
:class:`DataModel`: Emits the data_changed signal.
"""
self._list_widget.clear()
self._list_widget.addItems(items)
@Slot(str)
def show_error(self, message: str) -> None:
"""
Display an error message.
Args:
message: Error message to display.
"""
QMessageBox.critical(self, "Error", message)
Model/View Pattern Dokumentieren¶
from PySide6.QtCore import QAbstractListModel, QModelIndex, Qt
from typing import Any
class TodoListModel(QAbstractListModel):
"""
Model for a todo list.
This model provides data for Qt's view classes (QListView, QTableView, etc.)
following Qt's Model/View architecture.
The model stores todo items as strings and supports add/remove operations.
Data Roles:
Qt.ItemDataRole.DisplayRole: Item text for display.
Qt.ItemDataRole.EditRole: Item text for editing.
Qt.ItemDataRole.CheckStateRole: Todo completion state.
Example:
Use with a QListView::
model = TodoListModel()
model.add_todo("Buy milk")
model.add_todo("Write docs")
view = QListView()
view.setModel(model)
See Also:
https://doc.qt.io/qt-6/model-view-programming.html
"""
def __init__(self, todos: Optional[List[str]] = None):
"""
Initialize the todo model.
Args:
todos: Initial list of todos. Defaults to empty list.
"""
super().__init__()
self._todos = todos or []
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
"""
Return number of rows in the model.
Args:
parent: Parent index (unused for list models).
Returns:
Number of todo items.
"""
return len(self._todos)
def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
"""
Get data for the specified index and role.
Args:
index: Model index to retrieve data for.
role: Data role (DisplayRole, EditRole, etc.).
Returns:
Data for the specified role, or None if invalid.
Note:
This method is called by Qt views to retrieve display data.
"""
if not index.isValid():
return None
if role == Qt.ItemDataRole.DisplayRole:
return self._todos[index.row()]
return None
Sphinx Konfiguration¶
docs/source/conf.py¶
# Configuration file for the Sphinx documentation builder.
# Full list: https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
import sys
from pathlib import Path
# -- Path setup --------------------------------------------------------------
# Add project source to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'src'))
# -- Project information -----------------------------------------------------
project = 'My App'
copyright = '2025, Your Name'
author = 'Your Name'
release = '0.1.0'
version = '0.1'
# -- General configuration ---------------------------------------------------
extensions = [
# Sphinx built-in
'sphinx.ext.autodoc', # Auto-generate from docstrings
'sphinx.ext.napoleon', # Google/NumPy style support
'sphinx.ext.viewcode', # [source] links
'sphinx.ext.intersphinx', # Link to other projects
'sphinx.ext.todo', # TODO directives
'sphinx.ext.coverage', # Documentation coverage
'sphinx.ext.githubpages', # .nojekyll for GitHub Pages
# Third-party
'sphinx_rtd_theme', # Read the Docs theme
'sphinx_autodoc_typehints', # Better type hints
]
templates_path = ['_templates']
exclude_patterns = []
# Language
language = 'en'
# -- Options for HTML output -------------------------------------------------
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
# Theme options
html_theme_options = {
'navigation_depth': 4,
'collapse_navigation': False,
'sticky_navigation': True,
'includehidden': True,
'titles_only': False,
'logo_only': False,
}
# -- Extension configuration -------------------------------------------------
# Napoleon settings (Google/NumPy style)
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 = False
napoleon_use_admonition_for_notes = False
napoleon_use_admonition_for_references = False
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_preprocess_types = False
napoleon_type_aliases = None
napoleon_attr_annotations = True
# Autodoc settings
autodoc_default_options = {
'members': True,
'member-order': 'bysource',
'special-members': '__init__',
'undoc-members': True,
'exclude-members': '__weakref__'
}
autodoc_typehints = 'description'
autodoc_typehints_description_target = 'all'
# Type hints settings
set_type_checking_flag = True
typehints_fully_qualified = False
always_document_param_types = True
typehints_document_rtype = True
# Intersphinx mapping
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'PySide6': ('https://doc.qt.io/qtforpython-6/', None),
}
# TODO extension
todo_include_todos = True
# -- Custom CSS/JS -----------------------------------------------------------
html_css_files = [
'custom.css',
]
docs/source/_static/custom.css¶
/* Custom CSS for documentation */
/* Make code blocks more readable */
.highlight {
background-color: #f8f9fa;
}
/* Better spacing for parameters */
dl.field-list > dt {
font-weight: bold;
padding-bottom: 5px;
}
/* Signal/Slot styling */
.sig-name {
font-family: 'Consolas', 'Monaco', monospace;
}
/* Example boxes */
div.admonition.example {
background-color: #e7f3ff;
border-left: 4px solid #2196F3;
}
Auto-Documentation Setup¶
docs/source/index.rst¶
Welcome to My App's Documentation!
===================================
My App is a PySide6-based application following the MVP architecture pattern.
This documentation covers the API reference, guides, and examples.
.. toctree::
:maxdepth: 2
:caption: Contents:
getting_started
user_guide
api/index
development/index
changelog
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
docs/source/api/index.rst¶
API Reference
=============
This section documents the complete API of My App.
.. toctree::
:maxdepth: 2
models
views
controllers
utils
Core Modules
------------
Models
~~~~~~
.. automodule:: myapp.models
:members:
:undoc-members:
:show-inheritance:
Views
~~~~~
.. automodule:: myapp.views
:members:
:undoc-members:
:show-inheritance:
Controllers
~~~~~~~~~~~
.. automodule:: myapp.controllers
:members:
:undoc-members:
:show-inheritance:
docs/source/api/models.rst¶
Models
======
The models module contains all data models and business logic.
Data Model
----------
.. autoclass:: myapp.models.data_model.DataModel
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Project Model
-------------
.. autoclass:: myapp.models.project_model.ProjectModel
:members:
:undoc-members:
:show-inheritance:
Example Usage
-------------
Creating and using a data model::
from myapp.models.data_model import DataModel
# Create model
model = DataModel()
# Connect to signals
model.data_changed.connect(lambda items: print(f"Items: {items}"))
# Add items
model.add_item("Task 1")
model.add_item("Task 2")
# Save to file
model.save(Path("data.json"))
See Also
--------
* :class:`myapp.views.data_view.DataView` - View component
* :class:`myapp.controllers.data_controller.DataController` - Controller component
Makefile Targets¶
# docs/Makefile
.PHONY: help clean html livehtml
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " livehtml to auto-rebuild on changes"
@echo " clean to remove build files"
clean:
rm -rf build/
html:
sphinx-build -b html source build/html
@echo "Build finished. The HTML pages are in build/html."
livehtml:
sphinx-autobuild source build/html --open-browser
Auto-rebuild bei Änderungen:
Read the Docs Integration¶
.readthedocs.yaml¶
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/source/conf.py
fail_on_warning: false
formats:
- pdf
- epub
python:
install:
- method: pip
path: .
extra_requirements:
- docs
Requirements für RTD¶
docs/requirements.txt:
# Documentation dependencies for Read the Docs
sphinx>=7.0.0
sphinx-rtd-theme>=2.0.0
sphinx-autodoc-typehints>=1.24.0
myst-parser>=2.0.0
RTD Setup¶
- Account erstellen: https://readthedocs.org/
- Projekt importieren: Import a Project → GitHub
- Build starten: Automatic nach Git Push
- URL: https://myproject.readthedocs.io/
Versioning auf RTD¶
# .readthedocs.yaml
version: 2
sphinx:
configuration: docs/source/conf.py
python:
install:
- method: pip
path: .
# Build all versions
versions:
latest:
sphinx:
builder: html
stable:
sphinx:
builder: html
Badge in README.md¶
# My Project
[](https://myproject.readthedocs.io/en/latest/?badge=latest)
Documentation: https://myproject.readthedocs.io/
PyCharm Integration¶
Docstring Format Einstellen¶
Settings → Tools → Python Integrated Tools → Docstring format - Wähle: Google
Quick Documentation¶
Keyboard Shortcuts:
- Ctrl+Q (Windows/Linux) / F1 (macOS): Quick Documentation
- Ctrl+P: Parameter Info
- Ctrl+Shift+I: Quick Definition
Docstring Auto-Generation¶
def my_function(param1, param2):
"""
# Drücke Enter nach """ → PyCharm generiert:
Args:
param1:
param2:
Returns:
"""
Live Templates¶
Settings → Editor → Live Templates → Python
Neues Template erstellen: doc
External Tools für Sphinx¶
Settings → Tools → External Tools → Add
Name: Build Sphinx Docs
Program: make
Arguments: html
Working Directory: $ProjectFileDir$/docs
Shortcut: Ctrl+Shift+D
Best Practices¶
Was dokumentieren?¶
✅ IMMER dokumentieren:¶
1. Public API (Classes, Functions, Methods)
class DataModel(QObject):
"""
Core data model. # ← Immer dokumentieren
...
"""
def add_item(self, item: str) -> None:
"""Add an item.""" # ← Public method: Dokumentieren
pass
2. Komplexe Algorithmen
def calculate_optimal_path(
nodes: List[Node],
weights: Dict[Tuple[Node, Node], float]
) -> List[Node]:
"""
Calculate optimal path using Dijkstra's algorithm. # ← Komplex: Erklären
Implementation based on ...
Time complexity: O(n²)
Space complexity: O(n)
"""
pass
3. Nicht-offensichtliches Verhalten
def save(self, path: Path) -> bool:
"""
Save data to file.
Warning:
This method silently overwrites existing files without confirmation.
# ← Wichtige Warnung dokumentieren
"""
pass
4. Thread-Sicherheit
def process_data(self, data: bytes) -> None:
"""
Process data in background.
Note:
This method is thread-safe and can be called from any thread.
Results are emitted via the data_processed signal in the GUI thread.
# ← Thread-Verhalten dokumentieren
"""
pass
❌ NICHT dokumentieren:¶
1. Triviale Getter/Setter
2. Offensichtliche Funktionen
3. Private Implementation Details
Docstring Style Guide¶
Länge¶
# Kurz: Eine Zeile reicht
def clear(self) -> None:
"""Clear all items."""
pass
# Mittel: Kurze Beschreibung + Details
def load(self, path: Path) -> bool:
"""
Load data from file.
Returns:
True if successful, False otherwise.
"""
pass
# Lang: Vollständige Dokumentation
def process_complex_data(
self,
data: Dict[str, Any],
options: ProcessingOptions
) -> ProcessingResult:
"""
Process complex data with specified options.
This method performs multi-stage processing including validation,
transformation, and aggregation. The process is optimized for
large datasets and can run in a background thread.
Args:
data: Input data dictionary containing:
- 'items': List of items to process
- 'metadata': Processing metadata
options: Processing options controlling:
- Validation strictness
- Transformation rules
- Output format
Returns:
ProcessingResult containing:
- status: 'success', 'partial', or 'failed'
- processed_items: List of processed items
- errors: List of errors if any occurred
Raises:
ValueError: If data structure is invalid
ProcessingError: If processing fails
Example:
>>> data = {'items': [1, 2, 3], 'metadata': {}}
>>> options = ProcessingOptions(strict=True)
>>> result = processor.process_complex_data(data, options)
>>> result.status
'success'
Note:
This method is thread-safe and emits progress signals.
For very large datasets (>1M items), consider using
batch processing via :meth:`process_in_batches`.
See Also:
:meth:`process_in_batches`: Batch processing method
:class:`ProcessingOptions`: Options documentation
"""
pass
Type Hints + Docstrings¶
# ✅ Type Hints im Code, Beschreibung im Docstring
def calculate(price: float, tax_rate: float = 0.19) -> float:
"""
Calculate price including tax.
Args:
price: Base price before tax
tax_rate: Tax rate as decimal (0.19 = 19%)
Returns:
Total price including tax
"""
return price * (1 + tax_rate)
# ❌ Typen wiederholen ist überflüssig
def calculate(price, tax_rate=0.19):
"""
Calculate price including tax.
Args:
price (float): Base price before tax # ← Überflüssig wenn Type Hints
tax_rate (float): Tax rate # ← Redundant
Returns:
float: Total price # ← Überflüssig
"""
return price * (1 + tax_rate)
Cross-Referencing¶
class DataModel(QObject):
"""
Data model.
See Also:
:class:`DataView`: View component
:class:`DataController`: Controller component
:meth:`load`: Load data from file
:attr:`items`: List of items
"""
pass
def process(self, data: str) -> None:
"""
Process data.
Internally uses :func:`validate_data` and :func:`transform_data`.
See Also:
https://example.com/processing-guide
"""
pass
Writing Examples¶
Use code blocks with :: for examples in docstrings. Do NOT use >>> prefixes - they trigger
IDE syntax warnings and are meant for doctest-style executable examples.
Correct: Code Block Syntax¶
def complex_operation(a: int, b: int) -> int:
"""
Perform complex operation.
Example:
Basic usage::
result = complex_operation(5, 3)
# result: 8
With negative numbers::
result = complex_operation(-5, 3)
# result: -2
"""
pass
Incorrect: Doctest Syntax (Avoid)¶
def complex_operation(a: int, b: int) -> int:
"""
Example:
>>> complex_operation(5, 3) # Triggers IDE warnings
8
"""
pass
Multiple Examples¶
def load_data(path: Path) -> dict:
"""
Load data from file.
Example:
Load from JSON file::
data = load_data(Path("config.json"))
print(data["setting"])
Handle missing file::
try:
data = load_data(Path("missing.json"))
except FileNotFoundError:
data = {}
"""
pass
Inline Results¶
For simple cases, show results as comments:
def add(a: int, b: int) -> int:
"""
Add two numbers.
Example::
add(2, 3) # Returns: 5
add(-1, 1) # Returns: 0
"""
return a + b
Checklists¶
Pre-Commit Checklist¶
# 1. Docstrings vollständig?
ruff check --select D # Check docstrings
# 2. Type Hints korrekt?
mypy src/
# 3. Docs bauen erfolgreich?
cd docs && make html
# 4. Keine Sphinx Warnings?
cd docs && make html 2>&1 | grep -i warning
Release Checklist¶
- [ ] Alle public APIs dokumentiert
- [ ] Type Hints überall
- [ ] Examples getestet (doctest)
- [ ] Sphinx build erfolgreich
- [ ] RTD build erfolgreich
- [ ] Changelog aktualisiert
- [ ] Version Bump in
__init__.pyundconf.py
Tools & Commands¶
Lokale Dokumentation¶
# HTML Build
cd docs
make html
open build/html/index.html # macOS
xdg-open build/html/index.html # Linux
start build/html/index.html # Windows
# Live Preview (auto-rebuild)
pip install sphinx-autobuild
make livehtml
# Clean & Rebuild
make clean && make html
# Check for broken links
make linkcheck
Docstring Coverage¶
# Install interrogate
pip install interrogate
# Check coverage
interrogate src/ -vv
# Generate badge
interrogate -g src/
Code Quality¶
# Type checking
mypy src/
# Docstring linting
ruff check --select D src/
# Full check
ruff check src/
mypy src/
interrogate src/
Example Project Structure¶
myproject/
├── src/
│ └── myapp/
│ ├── __init__.py # """MyApp package."""
│ ├── models/
│ │ ├── __init__.py # """Data models."""
│ │ └── data_model.py # Full Google-style docs
│ ├── views/
│ │ ├── __init__.py
│ │ └── main_window.py # Full docs with signals
│ └── controllers/
│ ├── __init__.py
│ └── app_controller.py # Full docs with slots
├── docs/
│ ├── source/
│ │ ├── conf.py # Sphinx config
│ │ ├── index.rst
│ │ ├── api/
│ │ │ ├── index.rst
│ │ │ ├── models.rst
│ │ │ ├── views.rst
│ │ │ └── controllers.rst
│ │ ├── _static/
│ │ │ └── custom.css
│ │ └── _templates/
│ ├── build/
│ ├── Makefile
│ └── requirements.txt
├── tests/
├── .readthedocs.yaml
├── pyproject.toml
└── README.md
Quick Reference¶
Google Style Sections¶
"""
Brief description.
Detailed description (optional).
Args:
param: Description
Returns:
Description
Yields:
Description (for generators)
Raises:
ExceptionType: Description
Example:
Code example
Note:
Additional notes
Warning:
Warning message
See Also:
Related items
Todo:
Future improvements
Attributes:
attr: Description (for classes)
"""
Sphinx Directives¶
.. automodule:: myapp.models
:members:
:undoc-members:
:show-inheritance:
.. autoclass:: DataModel
:members:
:special-members: __init__
.. autofunction:: process_data
.. note:: This is a note
.. warning:: This is a warning
.. code-block:: python
code example
Resources¶
- Google Style Guide: https://google.github.io/styleguide/pyguide.html
- Sphinx Documentation: https://www.sphinx-doc.org/
- Napoleon Extension: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
- Read the Docs: https://docs.readthedocs.io/
- Type Hints PEP: https://www.python.org/dev/peps/pep-0484/
- Docstring PEP: https://www.python.org/dev/peps/pep-0257/
Happy Documenting! 📚