These rules define the expected coding style for this project.
They apply to all contributors:
- human developers
- AI-assisted tools
- coding agents
- automated systems
These rules are mandatory unless explicitly stated otherwise.
If a requested change conflicts with these rules, the change must stop and clarification must be requested.
The following rules are strict and MUST NOT be violated. When generating or modifying code, tools and agents MUST consult this file before producing changes.
- All code, comments, documentation, commit messages, and user-facing text MUST be written in English.
- Python typing rules MUST be respected (PEP 585 generics;
Optional[T], notT | None). from __future__ import annotationsMUST NOT be used.- Import ordering and grouping rules MUST be followed exactly.
- Functions and methods MUST NOT exceed 80 lines.
- Code changes MUST avoid modifying unrelated code.
- Naming MUST remain explicit and descriptive (no aggressive abbreviations).
- Code MUST remain mypy-friendly whenever possible.
AI-assisted tools and coding agents MUST read this file before generating or modifying code.
If a requested change conflicts with these rules, the tool MUST:
- stop the modification
- explain the conflict
- request clarification
Agents MUST prioritize deterministic rules over stylistic interpretation.
These principles explain why the rules exist. They protect consistency over time.
-
Determinism over preference
A rule must always produce the same result. Subjective rules lead to debates and inconsistency. -
Visual structure matters
Code is scanned more often than it is read. Structure reduces cognitive load. -
Automation-friendly, human-verifiable
Rules must be trivial to apply mechanically and easy for a human to verify. -
Clarity beats cleverness
Prefer explicit naming, typing, and control flow over smart shortcuts. -
Local consistency over global perfection
A consistent rule is more valuable than a perfect one applied inconsistently.
- All code, comments, documentation, commit messages, and free-form text MUST be written in English.
- This includes:
- Comments in code
- Docstrings
- Documentation files (README, guides, etc.)
- Commit messages
- Variable and function names
- Error messages and user-facing text
- No exceptions are allowed.
- Comments MUST be written in English.
- Comments MUST be concise and non-verbose.
- Do NOT describe obvious code behavior.
- Prefer explaining WHY something is done, not WHAT the code does.
- Prefer type hints over comments whenever possible.
# Avoid caching: data changes on every request.# This function takes a list of strings and returns an integer.- Commit messages MUST be concise and non-verbose.
- They serve as a brief summary of changes. Detailed explanations belong in the merge request description.
- The merge request description MUST be comprehensive and well-written.
- Variable, attribute, class, function, and parameter names MUST be descriptive.
- Names MUST NOT be aggressively shortened.
- Meaning and intent must remain explicit at all times.
- editor -> ed
- params -> par
- configuration -> cfg
- response -> resp
editor = get_editor()
request_params = parse_params(request)
self.editor = editor
self.request_params = request_paramsed = get_editor()
par = parse_params(request)
self.ed = ed
self.par = parThis project targets Python 3.14 and uses PEP 585 generics for standard collections.
- Use built-in generic containers:
list[T],dict[K, V],set[T],tuple[...]. - Do NOT use deprecated
typingaliases for standard collections (e.g.typing.List,typing.Dict,typing.Set,typing.Tuple). - Keep
typing.Optional[T]for optional types (do NOT useT | None). - Prefer explicit and accurate type hints.
- With mypy, avoid using # type: ignore whenever possible.
- Prefer type hints instead of comments.
from typing import Optional
def build_index(items: list[str]) -> dict[str, int]:
...
def find_user(users: list["User"]) -> Optional["User"]:
...from typing import Dict, List, Optional
def build_index(items: List[str]) -> Dict[str, int]:
...def find_user(users: list["User"]) -> "User | None":
...value = external_call() # type: ignore- Prefer string-based forward references for types when needed (e.g.
"MyType"). from __future__ import annotationsMUST NOT be used.typing.TYPE_CHECKINGMUST NOT be used in normal code.TYPE_CHECKINGis allowed only as a last resort to break unavoidable circular imports, and MUST include a clear inline reason comment.
def set_user(user: "User") -> None:
...from typing import Optional
class Node:
def __init__(self, parent: Optional["Node"] = None) -> None:
self.parent = parentfrom __future__ import annotationsfrom typing import TYPE_CHECKING
if TYPE_CHECKING:
from pkg.heavy import HeavyTypefrom typing import TYPE_CHECKING
if TYPE_CHECKING:
from pkg.heavy import HeavyType # TYPE_CHECKING: unavoidable circular importfrom ... import ...MUST be used only to import symbols (constants, classes, functions, variables).from ... import ...MUST NOT be used to import submodules/packages.- Submodules/packages MUST be imported using
import package.submodule. - If the imported name is intended to be used as a module namespace (e.g.
stc.SomeClass), it MUST be imported withimport ..., notfrom ... import ....
import wx.stc
from datetime import datetime, timezone
from wx.stc import StyledTextCtrl
from project.package import MyClass, my_functionfrom wx import stcImports MUST follow this exact order:
- import of builtin modules (alphabetically ordered)
- from ... import ... of builtin modules (ordered by increasing character length of the module path between
fromandimport)
(blank line)
- import of third-party packages
- from ... import ... of third-party packages (symbols only; never submodules)
(blank line)
- Absolute project imports only, with the following constraints:
- Imports MUST be grouped by package path.
- Groups MUST be ordered from the most distant to the closest package in the folder hierarchy.
- Each group MUST be separated by a single blank line.
- Inside each group, imports MUST be ordered by increasing number of characters (shorter paths first).
- Alphabetical order is secondary and applies only when path lengths are equal.
import os
import sys
from pathlib import Path
from datetime import datetime
import requests
import sqlalchemy
from pydantic import BaseModel
from sqlalchemy.orm import Session
from helpers.core.config import AppConfig
from helpers.core.logging import get_logger
from helpers.services.users import UserService
from helpers.services.payments import PaymentServiceimport os
import requests
from datetime import datetimefrom pathlib import Path
import pathlibimport os
from datetime import datetime
import requests
from pydantic import BaseModelfrom helpers.services.payments import PaymentService
from helpers.core.config import AppConfig
from helpers.services.users import UserServiceWhen multiple from ... import ... statements exist within the same import group,
they MUST be ordered by increasing character length of the module path:
the exact string between from and import (e.g. helpers.logger).
- Sorting key:
len(<module_path_between_from_and_import>), ascending (shorter first) - Tie-breaker (only when lengths are equal): alphabetical order of the module path
- Imported names on the right side of
importMUST NOT influence the order of statements
from helpers.logger import logger
from helpers.dataview import BaseDataViewListModel
from helpers.observables import ObservableListfrom helpers.dataview import BaseDataViewListModel
from helpers.logger import logger
from helpers.observables import ObservableListImported names MUST be ordered as follows:
- CONSTANTS (uppercase names)
- Classes
- Functions / methods
Each group MUST be ordered alphabetically.
- In general,
import ... as ...MUST NOT be used. - Import aliases are allowed only for widely established, conventional cases, such as:
import gettext as _import numpy as npimport pandas as pd
- Aliases that hide meaning are forbidden.
import numpy as np
import pandas as pd
import gettext as _import wx.stc as stcWhen importing builtin modules:
- Builtin imports without aliases MUST be grouped together.
- Builtin imports with aliases (
as) MUST be grouped separately. - Builtin imports with aliases MUST be ordered by **increasing character length of the string between
importandas**. - These two groups MUST be separated by a single blank line.
This rule applies only to builtin modules.
import os
import sys
import gettext as _
import numpy as np
import pandas as pdimport os
import gettext as _
import sysWhen importing multiple symbols from the same module:
- Parenthesized multiline
from ... import (...)MUST NOT be used. - Imports MUST NOT be split into one line per symbol.
- Prefer a single
from ... import ...line whenever possible. - If a
from ... import ...statement would exceed the maximum line width:- split it into multiple
from ... import ...statements - each resulting line MUST stay within the maximum line width
- each line MUST import as many symbols as possible
- keep the same import group ordering rules
- split it into multiple
from windows.components.stc.detectors import detect_syntax_id, is_base64, is_csv
from windows.components.stc.detectors import is_html, is_json, is_markdown
from windows.components.stc.detectors import is_regex, is_sql, is_xml, is_yamlfrom .detectors import is_regex, is_sql, is_xml
from .detectors import is_base64, is_csv, is_htmlfrom windows.components.stc.detectors import is_html
from windows.components.stc.detectors import is_json
from windows.components.stc.detectors import is_markdown
from windows.components.stc.detectors import is_regex
from windows.components.stc.detectors import is_sql
from windows.components.stc.detectors import is_xml
from windows.components.stc.detectors import is_yamlfrom .detectors import (
is_regex,
is_sql,
is_xml,
)- Lazy imports (imports inside functions or methods) MUST NOT be used.
- Lazy imports are allowed ONLY as a last resort when:
- There is an unavoidable circular import that cannot be resolved by refactoring
- The performance gain is critical and measurable (e.g., avoiding expensive module initialization)
- When lazy imports are used, they MUST include a clear inline comment explaining why they are necessary.
from windows.main import CURRENT_CONNECTION
def get_dialect() -> str:
connection = CURRENT_CONNECTION.get_value()
return connection.engine.value.dialectdef get_dialect() -> str:
from windows.main import CURRENT_CONNECTION # Lazy import without justification
connection = CURRENT_CONNECTION.get_value()
return connection.engine.value.dialectdef get_dialect() -> str:
from windows.main import CURRENT_CONNECTION # Lazy import: unavoidable circular dependency
connection = CURRENT_CONNECTION.get_value()
return connection.engine.value.dialectIf exact ordering cannot be determined automatically, prefer consistency with the surrounding file.
When defining multiple variables in sequence, variables MUST be ordered by increasing number of characters in the variable name (shorter names first).
This rule applies when:
- Variables are defined consecutively
- There is no logical dependency requiring a specific order
pos = self._editor.GetCurrentPos()
text = self._editor.GetText()text = self._editor.GetText()
pos = self._editor.GetCurrentPos()- Class names MUST be clear and descriptive.
- Method names MUST be clear and descriptive.
- Function names MUST be clear and descriptive.
- In wx-based UI code, on_* methods MUST handle only UI concerns:
- event routing
- validation
- user confirmation dialogs
- gathering UI input
- The actual action MUST be implemented in a do_* method.
- do_* methods SHOULD be UI-agnostic and testable (avoid dialogs and direct widget access when possible).
Class members MUST be ordered as follows:
__init__- Magic methods (
__str__,__repr__,__eq__, etc.) - Custom and other double-underscore methods (e.g.
__post_init__) - Private
@property - Public
@property - Private
@staticmethod - Public
@staticmethod - Private methods (names starting with
_) - Public methods
Within each visibility group (private or public), methods MUST be ordered as follows:
- By usage: a method MUST be defined before any other method that uses it.
- Alphabetically: if multiple methods are independent, they MUST be ordered alphabetically.
This rule applies independently to:
- private methods (names starting with
_) - public methods
class Example:
def _normalize(self, value: str) -> str:
return value.strip().lower()
def _validate(self, value: str) -> bool:
normalized = self._normalize(value)
return bool(normalized)
def process(self, value: str) -> bool:
return self._validate(value)class Example:
def _validate(self, value: str) -> bool:
normalized = self._normalize(value)
return bool(normalized)
def _normalize(self, value: str) -> str:
return value.strip().lower()- A function/method MUST be at most 80 lines.
- If it exceeds 80 lines, it MUST be split into smaller functions/methods with clear names.
- The walrus operator MUST be used whenever it avoids redundant calls or repeated expressions.
- It MUST NOT be used when it makes the control flow harder to read.
if (user := get_user()) is not None:
process_user(user)
while (line := file.readline()):
handle_line(line)user = get_user()
if user is not None:
process_user(user)- Code MUST be mypy-friendly.
- Do NOT silence errors with
# type: ignoreunless there is no reasonable alternative. - When
# type: ignoreis used, the reason MUST be explained clearly in an inline comment.
value = external_call() # type: ignore[attr-defined] # third-party lib exposes this dynamicallyvalue = external_call() # type: ignore