Skip to content

Python Documentation Guide

How documentation works in GehaSoftwareHub: writing docstrings, maintaining module READMEs, and publishing to the documentation site.


How the Documentation System Works

The documentation site is built with MkDocs Material and hosted on Cloudflare Pages at:

https://main.gehasoftwarehub.pages.dev

Source of Truth

Documentation lives in two places:

  1. docs/dev_guidelines/ -- Hand-written developer guides (this file, style guide, etc.)
  2. src/**/README.md -- Module-level documentation embedded next to the code

The deploy_docs.py script scans src/ for README.md files, generates wrapper pages in docs/api/, and builds the site. You never edit files in docs/api/ directly -- they are auto-generated and gitignored.

Project Structure

docs/
  index.md                  Hand-written landing page
  requirements.txt          MkDocs pip dependencies
  dev_guidelines/           Hand-written developer guides
  api/                      Auto-generated (gitignored)
docs_site_utils/
  mkdocs.yml                MkDocs configuration
  deploy_docs.py            Build and deploy script
  site/                     Build output (gitignored)
src/
  shared_services/
    README.md               Module overview (auto-discovered)
    file_operations/
      README.md             Sub-module docs (auto-discovered)
      api.py                Public API (auto-documented if present)

Publishing Documentation

Build and Deploy

python docs_site_utils/deploy_docs.py

This single command:

  1. Scans src/ for all README.md files
  2. Generates wrapper pages in docs/api/
  3. Updates the API Reference nav in mkdocs.yml
  4. Builds the static site with MkDocs
  5. Deploys to Cloudflare Pages

Build Only (Local Preview)

python docs_site_utils/deploy_docs.py --build
mkdocs serve -f docs_site_utils/mkdocs.yml

Then open http://127.0.0.1:8000.

One-Time Setup

# Install MkDocs dependencies
pip install -r docs/requirements.txt

# Install Cloudflare CLI (for deploy only)
npm install -g wrangler
wrangler login

Writing Module READMEs

Every module under src/ that should appear in the documentation needs a README.md file. The deploy script discovers these automatically.

What Gets Discovered

The script scans these top-level directories:

  • src/shared_services/ -- Shared Services category
  • src/custom_widgets/ -- Custom Widgets category
  • src/main_hub/ -- Main Hub category

Any README.md found recursively under these directories becomes a page.

README Template

# Module Name

One-line description of what this module does.

## Usage

\```python
from src.shared_services.my_module.api import my_function

result = my_function("input")
\```

## Key Classes

| Class | Purpose |
|-------|---------|
| `MyClass` | Does something specific |
| `MyOtherClass` | Does something else |

## Notes

- Important constraints or gotchas
- Thread safety information
- Dependencies on other modules

Rules

  • The first # Heading becomes the navigation label on the docs site
  • Keep READMEs focused on usage and architecture, not implementation details
  • If the module has an api.py, the script adds an auto-generated API Reference section using mkdocstrings (reads docstrings from the code)
  • No README = no docs page. Add one when you want the module documented.

Adding a New Module to the Docs

  1. Create src/your_module/README.md
  2. Run python docs_site_utils/deploy_docs.py
  3. Done. The script discovers it, generates the page, and updates the nav.

Writing Docstrings

We use Google Style docstrings. These are read by mkdocstrings to generate API reference pages for modules that have an api.py.

Module Docstring

"""
Observable application runtime state.

Provides a singleton for managing mutable application state with
Qt signals for reactive updates.

Usage:
    from src.shared_services.state.app_state import AppState

    state = AppState.instance()
    state.software_changed.connect(self.on_software_changed)
    state.set_active_software("PlantDesign")
"""

Class Docstring

class AppState(QObject):
    """
    Singleton managing observable application runtime state.

    Signals:
        software_changed: Emitted when active software changes.
            Args: software_name (str), is_active (bool)
        network_status_changed: Emitted when network connectivity changes.
            Args: connected (bool)

    Example:
        Basic usage::

            state = AppState.instance()
            state.software_changed.connect(self.handle_change)
            state.set_active_software("PlantDesign")
    """

Method Docstring

def set_active_software(self, name: str) -> None:
    """
    Set the active software module.

    Args:
        name: Name of the software module (e.g., "PlantDesign").
              Empty string clears the active software.

    Emits:
        software_changed: With (name, is_active) arguments.
    """

Property Docstring

@property
def is_software_active(self) -> bool:
    """Check if any software module is currently active."""
    return self._software_active

When to Write Docstrings

Always document:

  • Public classes and their __init__
  • Public methods with non-obvious behavior
  • Signals and what triggers them
  • Thread safety constraints
  • Methods in api.py files (these appear on the docs site)

Skip documentation for:

  • Trivial getters (return self._name)
  • Private helper methods (_internal_method)
  • __str__, __repr__ and similar obvious overrides

Docstring Sections Reference

"""
Brief description.

Longer description if needed.

Args:
    param: Description of parameter.

Returns:
    Description of return value.

Raises:
    ValueError: When something is invalid.

Example:
    Usage example::

        result = my_function("input")

Note:
    Additional context or caveats.

Warning:
    Important safety information.

See Also:
    :class:`RelatedClass`: Description.
"""

Type Hints

Use type hints on all public APIs. Mkdocstrings reads them automatically, so you do not need to repeat types in the docstring.

# Type hints in signature, descriptions in docstring
def load(self, file_path: Path, encoding: str = "utf-8") -> bool:
    """
    Load data from file.

    Args:
        file_path: Path to the file to load.
        encoding: File encoding. Defaults to UTF-8.

    Returns:
        True if loading succeeded.
    """

PySide6 Type Hints

from typing import Optional
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QWidget

class MyWidget(QWidget):
    """Custom widget with typed signals."""

    value_changed: Signal = Signal(int)

    def __init__(self, parent: Optional[QWidget] = None):
        super().__init__(parent)
        self._value: int = 0

Modern Syntax (Python 3.10+)

# Use | instead of Union/Optional
def process(value: int | str) -> bool | None:
    """Process a value."""
    pass

Writing Examples in Docstrings

Use code blocks with :: syntax. Do not use >>> prefixes (they trigger IDE warnings and are meant for doctest).

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
    """

PySide6-Specific Documentation

Documenting Signals

List signals in the class docstring with their arguments and when they fire:

class Worker(QRunnable):
    """
    Background worker for long-running tasks.

    Signals (via nested Signals class):
        progress(int): Percentage complete (0-100).
        finished(dict): Result dictionary with 'status' and 'data' keys.
        error(str): Error message if the task failed.
    """

Documenting Thread Safety

def upload_data(self, data: bytes) -> None:
    """
    Upload data to the server.

    This method starts a background thread via QThreadPool.
    Results are delivered via the upload_finished signal
    on the main thread.

    Note:
        Thread-safe. Can be called from any thread.
    """

PyCharm Integration

Docstring Format

Settings -> Tools -> Python Integrated Tools -> Docstring format: select Google

Useful Shortcuts

  • Ctrl+Q: Quick Documentation popup
  • Ctrl+P: Parameter Info
  • Type """ + Enter inside a function: auto-generates docstring skeleton

Editing the Documentation Site

Changing Developer Guidelines

Edit files directly in docs/dev_guidelines/. Changes appear on the site after the next deploy.

Changing the Landing Page

Edit docs/index.md.

Changing the MkDocs Configuration

Edit docs_site_utils/mkdocs.yml. The nav section for "API Reference" is auto-generated by the deploy script -- manual edits there will be overwritten. The "Home" and "Developer Guide" nav sections are safe to edit manually.

Adding MkDocs Features to Markdown

The site supports these Markdown extensions:

Admonitions (callout boxes):

!!! note "Title"
    Content of the note.

!!! warning "Title"
    Content of the warning.

??? info "Collapsible Section"
    This is collapsed by default.

Code blocks with syntax highlighting:

```python title="example.py"
from src.shared_services.logging.logger_factory import get_logger
logger = get_logger()
```

Content tabs:

=== "Windows"

    ```bash
    python deploy_docs.py
    ```

=== "Linux"

    ```bash
    python3 deploy_docs.py
    ```

Quick Reference

Day-to-Day Workflow

  1. Write code with Google Style docstrings
  2. Add or update README.md in your module
  3. Run python docs_site_utils/deploy_docs.py
  4. Documentation is live

Checklist for New Modules

  • [ ] README.md exists with # Heading and usage examples
  • [ ] Public API has docstrings (especially api.py)
  • [ ] Type hints on all public methods
  • [ ] Run deploy script to verify the page renders correctly