This document defines the coding standards and conventions for the PyFlowGraph project.
- Python 3.8+ compatibility required
- Follow PEP 8 with the following project-specific conventions
- Type hints required for all public methods and complex functions
- No emoji in code or comments
- No marketing language - keep documentation technical and professional
- All source code in
src/directory - One class per file for major components
- Related utility functions grouped in appropriate
*_utils.pyfiles - Test files in
tests/directory matching source structure
- Classes: PascalCase (e.g.,
NodeEditor,GraphExecutor) - Functions/Methods: snake_case (e.g.,
parse_function_signature,execute_node) - Constants: UPPER_SNAKE_CASE (e.g.,
DEFAULT_NODE_WIDTH) - Private Methods: Leading underscore (e.g.,
_validate_connection) - Qt Slots: Prefix with
on_(e.g.,on_node_selected)
# Standard library imports
import sys
import json
from typing import Dict, List, Optional, Tuple
# Third-party imports
from PySide6.QtCore import Qt, Signal, QPointF
from PySide6.QtWidgets import QWidget, QDialog
# Local imports
from src.node import Node
from src.pin import Pin- Required for all public methods
- Use
Optional[]for nullable types - Use
Union[]sparingly - prefer specific types - Example:
def create_node(self, node_type: str, position: QPointF) -> Optional[Node]:
pass- Docstrings for all public classes and methods
- Use triple quotes for docstrings
- No redundant comments - code should be self-documenting
- Example:
def execute_graph(self, start_node: Node) -> Dict[str, Any]:
"""Execute the graph starting from the specified node.
Args:
start_node: The node to begin execution from
Returns:
Dictionary of output values keyed by pin names
"""- Inherit from appropriate Qt base class
- Initialize parent in constructor
- Use layouts for responsive design
- Example:
class NodePropertiesDialog(QDialog):
def __init__(self, node: Node, parent=None):
super().__init__(parent)
self.node = node
self._setup_ui()- Define signals at class level
- Connect in constructor or setup method
- Disconnect when appropriate to prevent memory leaks
- Example:
class NodeEditor(QWidget):
node_selected = Signal(Node)
def __init__(self):
super().__init__()
self.node_selected.connect(self.on_node_selected)- Use context managers for file operations
- Clean up QGraphicsItems when removing from scene
- Properly parent Qt objects for automatic cleanup
- All graph files use clean JSON format
- Maintain human-readable formatting
- Example structure:
{
"nodes": [...],
"connections": [...],
"metadata": {
"version": "1.0",
"created": "2024-01-01"
}
}- Use
pathlib.Pathfor path operations - Always use absolute paths in tools
- Handle both Windows and Unix paths
- One test file per source module
- Test class names:
Test{ClassName} - Test method names:
test_{behavior}_when_{condition} - Example:
class TestNode(unittest.TestCase):
def test_pin_creation_when_type_hints_provided(self):
pass- Fast execution (< 5 seconds per test file)
- Deterministic - no flaky tests
- Test one behavior per test method
- Use setUp/tearDown for common initialization
CRITICAL: DO NOT USE MOCK OBJECTS WITH QT COMPONENTS
Qt widgets and QGraphicsItems require actual Qt object instantiation and cannot be mocked:
# ❌ FORBIDDEN - Causes Qt constructor errors
def test_with_mock(self):
mock_group = Mock()
pin = GroupInterfacePin(mock_group, "test", "input", "any") # FAILS
# ✅ CORRECT - Use real Qt objects or test fixtures
def test_with_real_objects(self):
app = QApplication.instance() or QApplication([])
group = Group("TestGroup")
pin = GroupInterfacePin(group, "test", "input", "any") # WORKSWhy Mock Fails with Qt:
- Qt constructors validate argument types at C++ level
- Mock objects don't inherit from Qt base classes
- QGraphicsItem requires proper parent/scene relationships
Alternative Testing Strategies:
- Use real Qt objects with minimal initialization
- Create test fixture classes that inherit from actual Qt classes
- Use dependency injection to isolate business logic from Qt dependencies
- Test Qt-independent logic separately from Qt-dependent rendering
- Raise specific exceptions with clear messages
- Catch exceptions at appropriate levels
- Never use bare
except:clauses - Example:
if not self.validate_connection(source, target):
raise ValueError(f"Invalid connection between {source} and {target}")- Display clear error messages in dialogs
- Log technical details for debugging
- Provide actionable error resolution hints
- All node code executes in isolated subprocesses
- Never use
eval()orexec()on untrusted input - Validate all inputs before processing
- Use JSON for inter-process communication
- Restrict file operations to project directories
- Validate file paths before operations
- Never expose absolute system paths in UI
- Profile before optimizing
- Cache expensive computations
- Use Qt's built-in optimization features
- Batch graphics updates when possible
- Clear references to deleted nodes/connections
- Use weak references where appropriate
- Monitor memory usage in long-running operations
- Clear, concise commit messages
- Focus on "why" not "what"
- No emoji in commit messages
- No attribution to AI tools in commits
- Example: "Fix connection validation for tuple types"
- Main branch for stable releases
- Feature branches for new development
- Fix branches for bug corrections
- Add emoji to code or comments
- Include marketing language in documentation
- Create files unless absolutely necessary
- Use
eval()orexec()on user input - Commit secrets or API keys
- Add AI attribution to commits or code
- Prefer editing existing files over creating new ones
- Follow existing patterns in the codebase
- Validate user inputs
- Use type hints for clarity
- Test error conditions
- Keep documentation technical and professional