Writing Documentation¶
This guide explains how to write effective docstrings for SATHI’s codebase. All docstrings are automatically extracted and included in the API reference documentation.
Docstring Style¶
SATHI uses Google-style docstrings, which are readable both in code and when rendered by Sphinx.
Basic Structure¶
def function_name(param1, param2):
"""Short one-line summary.
Longer description that provides more context about what the
function does, when to use it, and any important considerations.
Args:
param1 (type): Description of param1.
param2 (type): Description of param2.
Returns:
type: Description of return value.
Raises:
ExceptionType: When and why this exception is raised.
Examples:
>>> function_name('value1', 'value2')
'result'
"""
pass
Documenting Views¶
Django views should include information about:
Purpose of the view
Required permissions
URL parameters
Query parameters
Context variables passed to templates
Return type (rendered template, redirect, JSON response)
Function-Based Views¶
@login_required
@permission_required('patientapp.view_patient', raise_exception=True)
def prom_review(request, pk):
"""Display PRO Review page for a patient's questionnaire responses.
This view provides a comprehensive dashboard for reviewing patient-reported
outcomes over time, with filtering and population comparison capabilities.
Args:
request (HttpRequest): The HTTP request object.
pk (int): Primary key of the patient.
Query Parameters:
questionnaire_filter (str, optional): Filter by questionnaire ID.
time_range (str, optional): Number of submissions to display (default: '5').
max_time_interval (float, optional): Maximum time from start date.
time_interval (str, optional): Time unit ('days', 'weeks', 'months', 'years').
aggregation_type (str, optional): Type of aggregation ('median_iqr', 'mean_ci', etc.).
patient_filter_gender (str, optional): Gender filter for population comparison.
patient_filter_diagnosis (str, optional): Diagnosis filter for comparison.
patient_filter_treatment (str, optional): Treatment filter for comparison.
Returns:
HttpResponse: Rendered prom_review.html template with context containing:
- patient: Patient object
- assigned_questionnaires: QuerySet of PatientQuestionnaire objects
- submissions: QuerySet of QuestionnaireSubmission objects
- aggregated_patients: QuerySet for population comparison
- Various filter parameters
Raises:
Http404: If patient not found or user lacks access.
PermissionDenied: If user lacks view_patient permission.
Examples:
Access via URL: /patients/123/prom-review/
With filters: /patients/123/prom-review/?time_range=10&questionnaire_filter=5
"""
pass
Class-Based Views¶
class PatientListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
"""Display paginated list of patients with filtering capabilities.
This view provides a searchable, filterable list of patients accessible
to the current user based on their institution permissions.
Attributes:
model (Model): Patient model.
template_name (str): Template path.
context_object_name (str): Name for patient list in context.
paginate_by (int): Number of patients per page.
permission_required (str): Required permission string.
Query Parameters:
patient_select (str, optional): UUID of selected patient.
institution (str, optional): Filter by institution ID.
gender (str, optional): Filter by gender.
diagnosis (str, optional): Filter by diagnosis ID.
Context Variables:
patients: Paginated QuerySet of Patient objects.
institutions: QuerySet of Institution objects for filter dropdown.
diagnoses: QuerySet of Diagnosis objects for filter dropdown.
Examples:
Access via URL: /patients/
With filters: /patients/?gender=M&institution=5
"""
pass
Documenting Models¶
Django models should document:
Purpose of the model
Key relationships
Important fields
Custom methods
Validation rules
class Patient(models.Model):
"""Represents a patient in the SATHI system.
Patients are the primary subjects of PROM data collection. Each patient
belongs to an institution and can have multiple diagnoses, treatments,
and questionnaire submissions.
Attributes:
name (EncryptedCharField): Patient's full name (encrypted).
patient_id (EncryptedCharField): Unique patient identifier (encrypted).
date_of_birth (EncryptedDateField): Patient's date of birth (encrypted).
gender (str): Patient's gender (M/F/O).
institution (ForeignKey): Institution this patient belongs to.
diagnoses (ManyToManyField): Patient's diagnoses through PatientDiagnosis.
treatments (ManyToManyField): Patient's treatments through PatientTreatment.
Methods:
get_age(): Calculate patient's current age.
get_latest_submission(): Get most recent questionnaire submission.
has_active_questionnaires(): Check if patient has assigned questionnaires.
Examples:
>>> patient = Patient.objects.get(pk=1)
>>> patient.get_age()
45
>>> patient.diagnoses.count()
2
"""
name = EncryptedCharField(max_length=255)
patient_id = EncryptedCharField(max_length=100, unique=True)
def get_age(self):
"""Calculate patient's current age in years.
Returns:
int: Patient's age in complete years.
Examples:
>>> patient.date_of_birth = date(1980, 1, 1)
>>> patient.get_age()
44
"""
pass
Documenting Forms¶
Django forms should document:
Purpose of the form
Fields and their validation
Custom clean methods
Save behavior
class PatientForm(forms.ModelForm):
"""Form for creating and updating patient records.
This form handles patient demographic information with proper validation
for encrypted fields and institution assignment.
Fields:
name: Patient's full name (required).
patient_id: Unique identifier (required, validated for uniqueness).
date_of_birth: Patient's DOB (required, must be in past).
gender: Patient's gender (required, choices: M/F/O).
institution: Institution assignment (required).
Meta:
model (Model): Patient model.
fields (list): List of included fields.
Methods:
clean_patient_id(): Validate patient ID uniqueness.
clean_date_of_birth(): Ensure DOB is not in future.
Examples:
>>> form = PatientForm(data={'name': 'John Doe', ...})
>>> if form.is_valid():
... patient = form.save()
"""
class Meta:
model = Patient
fields = ['name', 'patient_id', 'date_of_birth', 'gender', 'institution']
def clean_patient_id(self):
"""Validate patient ID is unique within institution.
Returns:
str: Validated patient ID.
Raises:
ValidationError: If patient ID already exists in institution.
"""
pass
Documenting Utilities¶
Utility functions should document:
Purpose and use cases
Parameters and types
Return values
Side effects
Performance considerations
def calculate_construct_score(item_responses, equation, min_items=None):
"""Calculate construct score from item responses using equation.
This function evaluates a mathematical equation using item responses
as variables (Q1, Q2, etc.) and returns the calculated score.
Args:
item_responses (dict): Mapping of item numbers to response values.
Example: {1: 3, 2: 4, 3: 2}
equation (str): Mathematical equation using Q1, Q2, etc.
Example: "(Q1 + Q2 + Q3) / 3"
min_items (int, optional): Minimum items required for valid score.
If fewer items answered, returns None.
Returns:
float or None: Calculated score, or None if insufficient items.
Raises:
ValueError: If equation is invalid or contains undefined variables.
ZeroDivisionError: If equation results in division by zero.
Notes:
- Equation is evaluated in a restricted namespace for security
- Only mathematical operators and functions are allowed
- Missing item responses are treated as None
Examples:
>>> responses = {1: 3, 2: 4, 3: 5}
>>> calculate_construct_score(responses, "(Q1 + Q2 + Q3) / 3")
4.0
>>> responses = {1: 3, 2: 4} # Missing Q3
>>> calculate_construct_score(responses, "(Q1 + Q2 + Q3) / 3", min_items=3)
None
Performance:
O(n) where n is the number of items in the equation.
"""
pass
Documenting Classes¶
Utility classes should document:
Purpose and responsibilities
Initialization parameters
Public methods
Usage examples
class ConstructScoreData:
"""Container for construct score data with plot generation.
This class encapsulates all data related to a construct score including
historical values, population comparisons, and plot generation.
Args:
construct (ConstructScale): The construct scale being measured.
patient (Patient): Patient whose scores are being analyzed.
submissions (QuerySet): QuestionnaireSubmission objects to analyze.
generate_plot (bool, optional): Whether to generate plot. Default True.
aggregated_patients (QuerySet, optional): Patients for comparison.
aggregation_type (str, optional): Type of aggregation for comparison.
Attributes:
construct (ConstructScale): The construct scale.
current_score (float): Most recent score value.
previous_score (float): Previous score for comparison.
change_direction (str): Direction of change ('up', 'down', 'same').
is_beneficial (bool): Whether change is clinically beneficial.
plot_html (str): HTML for the plot, if generated.
Methods:
calculate_scores(): Calculate current and historical scores.
generate_plot(): Create plotly visualization.
get_change_indicator(): Get HTML for change indicator icon.
Examples:
>>> construct_data = ConstructScoreData(
... construct=construct,
... patient=patient,
... submissions=submissions
... )
>>> construct_data.current_score
75.5
>>> construct_data.change_direction
'up'
"""
def __init__(self, construct, patient, submissions, generate_plot=True):
"""Initialize construct score data container.
Args:
construct (ConstructScale): The construct scale.
patient (Patient): Patient whose scores are analyzed.
submissions (QuerySet): QuestionnaireSubmission objects.
generate_plot (bool, optional): Generate plot. Default True.
"""
pass
Special Sections¶
Notes¶
Use for important information:
"""
Notes:
- This function modifies the database
- Results are cached for 5 minutes
- Not thread-safe for concurrent modifications
"""
Warnings¶
Use for critical information:
"""
Warning:
This function performs expensive database queries.
Use sparingly and consider caching results.
"""
See Also¶
Use to reference related functions:
"""
See Also:
calculate_construct_score: For individual construct scoring
get_aggregated_scores: For population-level aggregation
"""
Best Practices¶
DO:
Write docstrings for all public functions, classes, and methods
Use clear, concise language
Include examples for complex functionality
Document all parameters and return values
Explain the “why” not just the “what”
Keep docstrings up-to-date when code changes
DON’T:
Write docstrings for obvious/trivial functions
Repeat the function name in the description
Include implementation details that may change
Use vague descriptions like “does stuff”
Forget to document exceptions
Leave outdated docstrings
Type Hints¶
Combine docstrings with type hints for maximum clarity:
from typing import Optional, List, Dict
def get_patient_scores(
patient_id: int,
construct_ids: Optional[List[int]] = None
) -> Dict[int, float]:
"""Get construct scores for a patient.
Args:
patient_id: Primary key of the patient.
construct_ids: List of construct IDs to retrieve.
If None, retrieves all constructs.
Returns:
Dictionary mapping construct IDs to their scores.
"""
pass
Checking Documentation¶
To verify your docstrings are properly formatted:
Build the documentation:
cd docs make html
Check for warnings:
Look for warnings about missing docstrings or formatting issues.
View the output:
Open
docs/build/html/developer/api_reference.htmlin a browser.Validate examples:
Ensure code examples in docstrings are correct and run without errors.
Tools¶
Linting Docstrings:
# Install pydocstyle
pip install pydocstyle
# Check docstrings
pydocstyle patientapp/views.py
Auto-generating Docstring Templates:
Many IDEs can generate docstring templates:
PyCharm: Type
"""and press EnterVS Code: Use autoDocstring extension
Vim: Use vim-pydocstring plugin