name: amdsmi-python-style-guide description: "ROCm Python style guide based on TheRock. Use when: writing Python code, reviewing Python PRs, checking Python style, creating Python scripts, type hints, error handling patterns, pathlib usage, argparse CLI design."
Python Style Guide (ROCm / TheRock)
Follows PEP 8, enforced by Ruff (amd-smi) or Black (TheRock) via pre-commit. Source: TheRock python_style_guide.md
Core Principles
- Fail fast and loud — false positives over silent corruption
- When in doubt, raise an exception
Type Hints
- Type hints on all function signatures
- Syntax that is compatible with python 3.6.8+
- Never use
Anyexcept in rare generic code - Never use
Optional[T],Union[X, Y],List,Dict— use built-in equivalents - Do NOT use
from __future__ import annotations
Extract Complex Types
- Type appears in multiple signatures →
NamedTupleorTypeAlias - Dict/tuple with 3+ fields →
dataclassorNamedTuple - If you document what tuple fields mean → use
NamedTuple
# Good
class KpackInfo(NamedTuple):
kpack_path: Path
size: int
kernel_count: int
# Bad
def get_info() -> tuple[Path, int, int]: ...
Dataclasses Over Tuples
Structured data with multiple fields → @dataclass or NamedTuple, not raw tuples.
Tuples OK for: simple pairs (x, y), stdlib unpacking, single-use immediate unpacking.
Function Calls & Arguments
- 3+ parameters or ambiguous types → use keyword arguments
- Multiple booleans → always use keyword arguments
# Good
result = build(amdgpu_family="gfx942", enable_testing=True, use_ccache=False)
# Bad
result = build("gfx942", True, False)
Code Organization
- All imports at top of file — inline only for documented circular deps (add comment explaining why)
- Scripts must have
if __name__ == "__main__":guard - CLI scripts must use
argparse(not rawsys.argv) - Access
argparseattrs directly:args.foo, notgetattr(args, "foo", default) - No duplicate code — extract to shared functions
- No magic numbers or fake estimates
- Methods < 30 lines, classes < 200 lines (ideally)
- If a class has 7+ responsibilities → split into focused classes
- God objects doing everything → multiple focused classes; 100+ line methods → extract helpers
Error Handling
| Rule | Detail |
|---|---|
| Fail fast | Never silently continue or produce incomplete results |
| Specific exceptions | FileNotFoundError, subprocess.CalledProcessError — not bare except Exception |
| Preserve chains | Always use from e when re-raising |
| Validate results | After critical ops: check file exists, non-empty, expected size |
| No binutils timeouts | Never timeout= on readelf, objcopy, etc. |
# Good — distinguish error conditions
try:
output = subprocess.check_output([readelf, "-S", str(file_path)])
except subprocess.CalledProcessError as e:
if e.returncode == 1:
return False # legitimate "not found"
raise RuntimeError(f"readelf failed: {e.output}") from e
except FileNotFoundError as e:
raise RuntimeError(f"readelf not found: {readelf}") from e
# Bad — swallows everything
try:
output = subprocess.check_output([readelf, "-S", str(file_path)])
except Exception:
return False
Filesystem & Paths
pathlib.Patheverywhere — noos.path, no string concatenation for paths- No assumptions about CWD — derive from
Path(__file__).resolve().parent - No hard-coded project paths — use env vars,
tempfile, or relative to__file__
# Good
THIS_DIR = Path(__file__).resolve().parent
config = THIS_DIR / "config.json"
# Bad
config = Path("build_tools/config.json") # assumes CWD
config = Path("/home/user/project/config.json") # hard-coded
Performance
- Compile regexes at module level, not inside functions
- Check cheap conditions before expensive ones (magic bytes before subprocess)
- Cache expensive computations when called repeatedly
- Use generators for large datasets
Testing
- Verify fail-fast behavior with
pytest.raises - Use real temp files over mocks for filesystem operations
- Mock only external dependencies (network, expensive tools)
- Integration tests should exercise the full path
amd-smi Python Conventions
Naming
py-interface/amdsmi_interface.pymirrors C API names (amdsmi_get_*,amdsmi_set_*)amdsmi_cli/usessnake_casemethods- Private functions/methods:
_prefix
Project Layout
| Directory | Purpose |
|---|---|
py-interface/ |
Python bindings — amdsmi_interface.py, amdsmi_wrapper.py, amdsmi_exception.py |
amdsmi_cli/ |
CLI tool — amdsmi_commands.py, amdsmi_parser.py, amdsmi_helpers.py |
Critical Python Files (high churn — review carefully)
amdsmi_cli/amdsmi_commands.py— CLI behavior regressions, output format changespy-interface/amdsmi_interface.py— must stay in sync with C headeramdsmi.hpy-interface/amdsmi_wrapper.py— generated bindings + library loader (see loader rules below)amdsmi_cli/amdsmi_parser.py— argument parsing
Library Loader Rules (amdsmi_wrapper.py)
Changes to _detect_install_context, _build_candidate_paths, or _load_library are ❌ BLOCKING if they break system or pip install context. Verify:
Path(__file__).resolve()used correctly- pip detection logic intact
_libraries['libamd_smi.so']key preserved- Sync with
tools/generator.py
Python Testing
- Tests must work with both system-installed and pip-installed amdsmi
- CLI tests live in
amdsmi_cli/ - Verify fail-fast behavior with
pytest.raises - Use real temp files over mocks for filesystem operations
- Mock only external dependencies (network, expensive tools)
- Integration tests should exercise the full path
Review Checklist
When reviewing Python code, verify:
- Type hints on all functions — modern syntax, no
Any - No
from __future__ import annotations - Complex types extracted to
NamedTuple/dataclass - Specific exception handling — no bare
except Exception - Fail-fast — no silent
continueon errors -
from eon all re-raises -
pathlib.Pathfor all filesystem ops - No CWD assumptions, no hard-coded paths
- No magic numbers or fake estimates
- No duplicate code
-
argparsefor CLI,__main__guard for scripts - Keyword args for complex function calls
- All imports at top (inline only with circular-dep comment)
- No timeouts on binutils
- Output validation after critical operations
- Methods < 30 lines, classes < 200 lines