8.1 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. ImGui Defer Patterns
To prevent PopID or End leaks in immediate-mode rendering, and to keep code flat (0-1 levels of nesting) for AI agents, use the following patterns:
-
The Context Manager Pattern (Mandatory for complex blocks): Wrap all
Begin/Endblocks inimscopecontext managers (fromsrc/imgui_scopes.py).with imscope.window("My Window") as (exp, opened): if exp: imgui.text("Hello") with imscope.tab_item("My Tab") as (exp, _): if exp: self._render_tab_content()This adds only 1 space of indentation (project standard) and guarantees the corresponding
Endis called even on early returns or exceptions. Crucial: Always check theexp(expanded/visible) state before rendering content to avoid ID conflicts and performance overhead. -
The Flat Dispatch Pattern (Recommended for the main loop): To avoid nesting multiple window checks, use a dispatch helper that encapsulates the state check and the scope.
self._render_window_if_open("My Window", self._render_my_panel)This keeps the main GUI loop as a flat sequence of declarative calls.
12. 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]
"""
...