Back to Fastled

Python Code Standards

docs/agents/python-standards.md

3.10.33.0 KB
Original Source

Python Code Standards

Type Annotations

Use modern PEP 585 and PEP 604 type hints (Python 3.11+ native syntax):

  • Use list[T], dict[K, V], set[T] instead of List[T], Dict[K, V], Set[T]
  • Use X | Y for unions instead of Union[X, Y]
  • Use T | None instead of Optional[T]
  • No need for from __future__ import annotations (works natively in Python 3.11+)

Structured Return Types

Prefer @dataclass over tuples and dicts for function return types:

  • Correct: Return a @dataclass with named fields (e.g., CompileResult(success=True, error_output=""))
  • Avoid: Returning tuple[bool, str] or dict[str, Any] -- unnamed fields are error-prone and hard to read
  • Named fields are self-documenting, support IDE autocomplete, and make refactoring safer
  • Exception: Single-value returns or well-established conventions (e.g., divmod) don't need a dataclass

Exception Handling - KeyboardInterrupt

  • CRITICAL: NEVER silently catch or suppress KeyboardInterrupt exceptions
  • When catching broad Exception, ALWAYS explicitly re-raise KeyboardInterrupt first:
    python
    try:
        operation()
    except KeyboardInterrupt:
        raise  # MANDATORY: Always re-raise KeyboardInterrupt
    except Exception as e:
        logger.error(f"Operation failed: {e}")
    
  • Thread-aware KeyboardInterrupt handling: When handling KeyboardInterrupt in worker threads, use the global interrupt handler:
    python
    from ci.util.global_interrupt_handler import notify_main_thread
    
    try:
        # Worker thread operation
        operation()
    except KeyboardInterrupt:
        print("KeyboardInterrupt: Cancelling operation")
        notify_main_thread()  # Propagate interrupt to main thread
        raise
    
  • notify_main_thread() calls _thread.interrupt_main() to ensure the main thread receives the interrupt signal
  • This is essential for multi-threaded applications to properly coordinate shutdown across all threads
  • KeyboardInterrupt (Ctrl+C) is a user signal to stop execution - suppressing it creates unresponsive processes
  • This applies to ALL exception handlers, including hook handlers, cleanup code, and background tasks
  • Ruff's BLE001 (blind-except) rule can detect this but is NOT active by default
  • Consider enabling BLE001 with --select BLE001 for automatic detection

Process Execution - No bare subprocess

  • NEVER use subprocess.run() or subprocess.Popen() directly — use RunningProcess.run() instead
  • RunningProcess (from the running_process package) wraps subprocess with proper timeout handling, streaming, and interrupt propagation
  • subprocess is problematic: missing real-time output streaming, inconsistent timeout behavior, and no integration with the project's interrupt handling
  • Correct:
    python
    from running_process import RunningProcess
    result = RunningProcess.run(["git", "status"], check=False, timeout=10, capture_output=True, text=True)
    print(result.stdout)
    
  • Wrong:
    python
    import subprocess
    result = subprocess.run(["git", "status"], capture_output=True, text=True)