Docstring Style Guide¶
This guide explains how to write docstrings for pydvma that will automatically appear in the documentation.
Overview¶
pydvma uses mkdocstrings to automatically extract documentation from Python docstrings. When you update docstrings in the source code, the changes automatically appear in the online documentation.
Docstring Format¶
The codebase currently uses a simple format. You can use either:
- Simple Args format (current style in most of the codebase)
- Google style (recommended for new docstrings)
Both will be parsed correctly by mkdocstrings.
Simple Args Format (Current)¶
def calculate_fft(time_data, time_range=None, window=None):
'''
Calculate FFT of time domain data.
Args:
time_data (<TimeData> object): time series data
time_range: 2x1 numpy array to specify data segment to use
window (str): window function name ('hann', 'blackman', etc.)
Returns:
<FreqData> object containing frequency domain data
'''
# Implementation...
Google Style Format (Recommended for New Code)¶
def calculate_fft(time_data, time_range=None, window=None):
"""Calculate FFT of time domain data.
Computes the Fast Fourier Transform of the input time series data,
with optional windowing and time range selection.
Args:
time_data (TimeData): Time series data to transform
time_range (array-like, optional): 2-element array [start, end]
specifying the time segment to use. If None, uses all data.
window (str, optional): Window function name. Options include
'hann', 'hamming', 'blackman', or None for rectangular window.
Defaults to None.
Returns:
FreqData: Frequency domain representation of the input data,
containing the complex spectrum and frequency axis.
Examples:
>>> freq_data = calculate_fft(time_data, window='hann')
>>> magnitude = np.abs(freq_data.freq_data)
Notes:
The frequency resolution is determined by the duration of the
selected time segment: Δf = 1/duration
"""
# Implementation...
Class Docstrings¶
class TimeData:
"""Container for time-domain measurement data.
Stores voltage samples along with metadata, settings, and axis
information. Supports multi-channel data.
Attributes:
time_axis (ndarray): Sample times in seconds.
time_data (ndarray): Shape (n_samples, n_channels), in volts.
settings (MySettings): Acquisition settings snapshot.
units (list[str] or None): Engineering units per channel.
channel_cal_factors (ndarray): Per-channel V→eu multipliers.
test_name (str or None): Free-form label.
unique_id (uuid.UUID): Generated at construction.
Examples:
>>> time_data = TimeData(t, y, settings, test_name='test_01')
>>> channel_0 = time_data.time_data[:, 0]
"""
def __init__(self, time_axis, time_data, settings, timestamp=None,
timestring=None, units=None, channel_cal_factors=None,
id_link=None, test_name=None):
"""Initialise TimeData.
Args:
time_axis (ndarray): Sample times in seconds.
time_data (ndarray): Voltage samples, (n_samples, n_channels).
settings (MySettings): Acquisition snapshot.
timestamp (datetime, optional): Capture start. Defaults to now().
timestring (str, optional): Filesystem-safe stamp.
units (list[str], optional): Per-channel units.
channel_cal_factors (ndarray, optional): V→eu factors.
Defaults to all-ones.
id_link (uuid.UUID, optional): Link to a parent TimeData.
test_name (str, optional): Free-form label.
"""
# Implementation...
Type Hints¶
Add type hints where possible:
from typing import Optional, Union, Tuple
import numpy as np
def calculate_tf(
time_data: 'TimeData',
ch_in: int = 0,
time_range: Optional[np.ndarray] = None,
window: Optional[str] = None,
N_frames: int = 1,
overlap: float = 0.5
) -> 'TfData':
"""Calculate transfer function from time domain data.
Args:
time_data: Input time series data
ch_in: Index of input channel
time_range: Time segment [start, end] or None for all data
window: Window function name or None
N_frames: Number of segments for averaging
overlap: Overlap fraction between segments (0 to 1)
Returns:
Transfer function data with coherence
"""
# Implementation...
Sections in Docstrings¶
Common sections (all optional):
- Args: Function/method parameters
- Returns: Return value description
- Raises: Exceptions that may be raised
- Examples: Usage examples
- Notes: Additional information
- References: Citations or links
- See Also: Related functions
- Warnings: Important warnings for users
Examples Section¶
Provide practical examples:
def calculate_damping_from_sono(time_data, n_chan=1, nperseg=None, start_time=None):
"""Calculate damping from sonogram analysis of free decay.
Analyzes the decay of spectral peaks in a sonogram to extract
modal parameters including natural frequencies and damping ratios.
Args:
time_data (TimeData): Time series containing free decay response
n_chan (int): Channel index to analyze. Defaults to 1.
nperseg (int, optional): FFT segment length for sonogram.
If None, automatically determined based on signal length.
start_time (float, optional): Time to start analysis in seconds.
If None, automatically detected from pretrigger settings.
Returns:
tuple: A tuple containing:
- fn (ndarray): Natural frequencies in Hz
- Qn (ndarray): Quality factors (Q = 1/(2ζ))
- fit_data (dict): Dictionary with fit visualization data:
- 't': time axis
- 'fits': list of fit dictionaries for each mode
Examples:
>>> # Single impact test
>>> fn, Qn, fit_data = calculate_damping_from_sono(time_data, n_chan=1)
>>> zeta = 1 / (2 * Qn) # Convert Q to damping ratio
>>> print(f"Mode 1: f={fn[0]:.1f} Hz, ζ={zeta[0]:.4f}")
>>> # Specify custom parameters
>>> fn, Qn, fit_data = calculate_damping_from_sono(
... time_data,
... n_chan=0,
... nperseg=1024,
... start_time=0.05
... )
Notes:
The method identifies frequency peaks in the initial spectrum and
tracks their exponential decay over time. Better results are obtained
with:
- Good signal-to-noise ratio
- Sufficient decay duration (several oscillation periods)
- Appropriate nperseg choice based on frequency spacing
See Also:
calculate_sonogram: Compute the underlying sonogram
calculate_tf: Alternative method for modal identification
"""
# Implementation...
What Gets Documented¶
mkdocstrings automatically extracts:
- ✅ Function and method signatures
- ✅ Parameter descriptions from docstrings
- ✅ Return value descriptions
- ✅ Class attributes
- ✅ Examples
- ✅ Type hints (if provided)
Testing Your Docstrings¶
Local Preview¶
# Install dependencies
pip install -r requirements-docs.txt
# Serve documentation locally
mkdocs serve
Navigate to the API reference page for your module to see how it looks.
Docstring Linting¶
Use pydocstyle to check docstring quality:
Migration Strategy¶
To gradually improve documentation:
- New code: Use Google-style docstrings with type hints
- Existing code: Update docstrings as you modify functions
- Priority: Focus on user-facing functions first
- No rush: The current simple format works; improve over time
Best Practices¶
Do ✅¶
- Describe what the function does in one clear sentence
- Document all parameters and return values
- Provide at least one usage example
- Explain units (Hz, seconds, etc.)
- Note important limitations or assumptions
Don't ❌¶
- Don't duplicate information already in type hints
- Don't describe implementation details (use code comments instead)
- Don't write overly long docstrings (link to user guide for details)
- Don't forget to update docstrings when changing function behavior
Live Documentation Updates¶
When you push changes to GitHub:
- Commit updated source files with new docstrings
- Push to master/main branch
- GitHub Actions automatically rebuilds documentation
- Changes appear online within minutes
No need to manually update API reference pages - mkdocstrings handles it automatically!