b9c1b63f8d
- 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.
153 lines
7.0 KiB
Markdown
153 lines
7.0 KiB
Markdown
# 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 = 5` in 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 | None` over `Optional[X]` where Python 3.10+ is available. Use `from __future__ import annotations` if needed.
|
|
- **Callable:** Use bare `Callable` for callback factories. Use `Callable[[ArgTypes], ReturnType]` when the signature is known and stable.
|
|
- **DearPyGui / ImGui callbacks:** Use `sender: Any, app_data: Any` for framework callbacks where the types are runtime-determined.
|
|
|
|
## 3. Imports
|
|
|
|
- Use `from __future__ import annotations` at the top of every module.
|
|
- Group imports: stdlib, third-party, local — separated by a blank line.
|
|
- Use `from typing import Any, Optional, Callable` etc. for type-only imports.
|
|
- Prefer `from x import Y` for specific symbols over `import x` when 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:` over `if 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__.py` files.**
|
|
- **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:
|
|
|
|
```python
|
|
# 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:
|
|
1. It holds mutable state that must be encapsulated
|
|
2. It has 3+ related methods that share state
|
|
3. It implements a behavioral interface used polymorphically (not just data grouping)
|
|
|
|
### Refactoring Existing Classes (Strangler Fig Pattern)
|
|
When refactoring a class to functions:
|
|
1. Write test validating current behavior (prevents regression)
|
|
2. Extract one method at a time into module-level functions
|
|
3. Create wrapper function that delegates to class until migration complete
|
|
4. Delete class only when ALL callers migrated
|
|
5. Commit with `refactor(oop):` prefix
|
|
|
|
### Data Structures
|
|
- **Data-only containers:** Use `NamedTuple`, `dataclass(frozen=True)`, or plain `dict` — 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 functions
|
|
- `PLR6301`: No public methods — class is a namespace anti-pattern
|
|
- `PLR0206`: Descriptors in class body — use simple attributes
|
|
|
|
### Enforcement
|
|
```toml
|
|
[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:
|
|
```python
|
|
def start_services(self) -> None:
|
|
"""
|
|
Initialises background threads and MCP servers.
|
|
[C: App.run, _cb_load_project]
|
|
"""
|
|
...
|
|
```
|