- Add section 10 (Anti-OOP Conventions) to python.md with hard rules, class justification requirements, and Strangler Fig refactoring pattern - Create conductor/refactor_oop.md tracker with 4 phases for class elimination - Add ruff PLR rules (PLR0912, PLR6301, PLR0206) to pyproject.toml for OOP anti-patterns Addresses AI agent scope misinterpretation issues by enforcing flat function-call graphs over deep class hierarchies.
7.0 KiB
AI-Optimized Python Style Guide
This document defines the Python style conventions for the Manual Slop codebase. These deviate from PEP 8 / Google style to minimize token consumption when code is processed by AI agents, while preserving readability for human review.
1. Indentation and Whitespace
- Indentation: 1 space per level. No tabs.
- Continuation lines: 1 space relative to the opening construct.
- Blank lines: Zero blank lines between function/method definitions within a class. One blank line between top-level definitions only when separating logically distinct sections.
- Trailing whitespace: None.
- Rationale: 1-space indentation reduces token count by ~40% compared to 4-space on deeply nested GUI code, with no loss of structural clarity for AST-based tools.
Maximum Nesting Depth
- Hard limit: 5 levels maximum.
- AI agents consistently misinterpret Python scope via indentation. This hard limit prevents deeply nested blocks that confuse model interpretation.
- Classes and async handlers typically consume 1-2 levels before business logic begins, so 5 accommodates real-world usage patterns.
- Enforcement: Use ruff
[tool.ruff.lint.mccabe] max-complexity = 5in the project linter config. - Refactoring mandate: Any block exceeding 5 levels must be extracted into a named function. Do not "work around" this with tricks—extract the logic.
- Rationale: GUI callbacks and async handlers naturally want to nest. This constraint forces extraction of deep logic into testable, named units.
2. Type Annotations
- All functions and methods must have return type annotations.
- All parameters (except
self/cls) must have type annotations. - Module-level and class-level variables must have type annotations.
- Use modern syntax:
list[str],dict[str, Any],X | NoneoverOptional[X]where Python 3.10+ is available. Usefrom __future__ import annotationsif needed. - Callable: Use bare
Callablefor callback factories. UseCallable[[ArgTypes], ReturnType]when the signature is known and stable. - DearPyGui / ImGui callbacks: Use
sender: Any, app_data: Anyfor framework callbacks where the types are runtime-determined.
3. Imports
- Use
from __future__ import annotationsat the top of every module. - Group imports: stdlib, third-party, local — separated by a blank line.
- Use
from typing import Any, Optional, Callableetc. for type-only imports. - Prefer
from x import Yfor specific symbols overimport xwhen only one or two names are used.
4. Naming
- snake_case for modules, functions, methods, variables.
- PascalCase for classes.
- ALL_CAPS for module-level constants.
- Single leading underscore (
_name) for internal/private members.
5. Docstrings
- Required on classes and non-trivial public functions.
- Use
"""triple double quotes""". - One-line summary is sufficient for simple methods.
- Omit docstrings on obvious internal methods (e.g.,
_cb_*callbacks,_render_*UI methods) where the name is self-documenting.
6. String Formatting
- Prefer f-strings.
- Use double quotes (
") for strings by default. - Use single quotes when the string contains double quotes.
7. Error Handling
- Never use bare
except:. - Use specific exception types.
- Prefer
if x is None:overif not x:when testing for None specifically.
8. AI-Agent Specific Conventions
- No redundant comments. Do not add comments that restate what the code does. Only comment on why when non-obvious.
- No empty
__init__.pyfiles. - Minimal blank lines. Token-efficient density is preferred over visual padding.
- Short variable names are acceptable in tight scopes (loop vars, lambdas). Use descriptive names for module-level and class attributes.
10. Anti-OOP Conventions
Philosophy
AI agents consistently misinterpret class hierarchies, method resolution, and inheritance. Flat function-call graphs are deterministic and traceable. OOP introduces scoping complexity that compounds with indentation.
Hard Rules (Enforced by lint)
- Never write a class for a single method. Use a function.
- Never use inheritance for code reuse. Compose with standalone functions.
- Never use private methods (
_method). Module-level functions with clear names suffice. - No nested classes. Define helper types at module level.
- No decorator classes. Use plain functions with decorators.
Class Justification Required
Every class definition MUST include a comment explaining WHY it is a class and not a function group or struct:
# JUSTIFIED: Holds mutable shared state across multiple async operations
# Cannot be replaced by functions without passing state through every call
class AsyncOperationScheduler:
...
# NOT JUSTIFIED: Simply groups related functions
# Should be module-level functions in operations.py
class OperationHelper:
def validate(self): ...
def execute(self): ...
Acceptability Criteria
A class is justified ONLY when ALL of:
- It holds mutable state that must be encapsulated
- It has 3+ related methods that share state
- It implements a behavioral interface used polymorphically (not just data grouping)
Refactoring Existing Classes (Strangler Fig Pattern)
When refactoring a class to functions:
- Write test validating current behavior (prevents regression)
- Extract one method at a time into module-level functions
- Create wrapper function that delegates to class until migration complete
- Delete class only when ALL callers migrated
- Commit with
refactor(oop):prefix
Data Structures
- Data-only containers: Use
NamedTuple,dataclass(frozen=True), or plaindict— NOT classes - State machines: Use dict-based transitions, not class + inheritance
- Configuration: Plain dict or
TypedDict, not classes with defaults
Anti-Patterns (Flagged by Ruff PLR rules)
PLR0912: Too many branches — extract to functionsPLR6301: No public methods — class is a namespace anti-patternPLR0206: Descriptors in class body — use simple attributes
Enforcement
[tool.ruff.lint.select]
select = ["E", "F", "W", "C90", "C4", "PLR0912", "PLR6301", "PLR0206"]
[tool.ruff.lint.plr]
max-returns = 4
max-locals = 8
max-args = 5
11. Structural Dependency Mapping (SDM)
To assist AI agents in evaluating refactoring impact across dynamic codebases, all major definitions SHOULD include terse SDM tags at the end of their docstrings.
- Format: Tags are enclosed in square brackets at the end of the docstring body.
- For Functions/Methods:
[C: CallerA, CallerB]— List of primary internal callers within the codebase. - For State Variables:
[M: File:Line, Method]— List of primary mutation points (where the value is assigned).[U: File]— Major codepaths of use (where the value is read but not changed).
Example:
def start_services(self) -> None:
"""
Initialises background threads and MCP servers.
[C: App.run, _cb_load_project]
"""
...