Python code style guide
Applies Sun Lab Python coding conventions.
You MUST read this skill and load the relevant reference files before writing or modifying Python code. You MUST verify your changes against the checklist before submitting.
Scope
Covers:
- •Python code style (docstrings, type annotations, naming, formatting, error handling)
- •Ataraxis library usage preferences (console, timing, data structures)
- •File organization, import conventions, and module structure
- •Click CLI conventions
- •Enum, dataclass, and class design patterns
- •Test file conventions
Does not cover:
- •README file conventions (invoke
/readme-style) - •Commit message conventions (invoke
/commit) - •Skill file and CLAUDE.md conventions (invoke
/skill-design) - •Codebase exploration workflows (invoke
/explore-codebase)
Workflow
You MUST follow these steps when this skill is invoked.
Step 1: Read this skill
Read this entire file. The core conventions below apply to ALL Python code.
Step 2: Load relevant reference files
Based on the task, load the appropriate reference files:
| Task | Reference to load |
|---|---|
| Writing or modifying docstrings/types | docstrings-and-types.md |
| Writing classes, dataclasses, or enums | class-patterns.md |
| Using ataraxis libs, Numba, Click, tests | libraries-and-tools.md |
| Using ataraxis library features | Invoke /explore-dependencies first, then load above |
| Reviewing code before submission | anti-patterns.md |
Load multiple references when the task spans multiple domains.
Step 3: Apply conventions
Write or modify Python code following all conventions from this file and the loaded references.
Step 4: Verify compliance
Complete the verification checklist at the end of this file. Every item must pass before submitting work. For anti-pattern examples, load anti-patterns.md.
Naming conventions
Variables
Use full words, not abbreviations:
| Avoid | Prefer |
|---|---|
t, t_sq | interpolation_factor, t_squared |
coeff, coeffs | coefficient, coefficients |
pos, idx | position, index |
img, val | image, value |
Functions
- •Use descriptive verb phrases:
compute_coefficients,extract_features - •Private functions start with underscore:
_process_batch,_validate_input - •Avoid generic names like
process,handle,do_something
Constants
Module-level constants with type annotations and descriptive names:
# Minimum number of samples required for statistical validity. _MINIMUM_SAMPLE_COUNT: int = 100
Function calls
Always use keyword arguments for clarity:
# Good np.zeros((4,), dtype=np.float32) compute_coefficients(interpolation_factor=t, output=result) # Avoid np.zeros((4,), np.float32) compute_coefficients(t, result)
Exception: Single positional arguments for obvious cases like range(4), len(array).
Error handling
In projects that depend on ataraxis-base-utilities, use console.error for error reporting:
from ataraxis_base_utilities import console
def process_data(self, data: NDArray[np.float32], threshold: float) -> None:
if not (0 < threshold <= 1):
message = (
f"Unable to process data with the given threshold. The threshold must be in range "
f"(0, 1], but got {threshold}."
)
console.error(message=message, error=ValueError)
In projects that do not depend on ataraxis-base-utilities, use standard raise with the same
message format.
Error message format
- •Start with context: "Unable to [action] using [input]."
- •Explain the constraint: "The [parameter] must be [constraint]"
- •Show actual value: "but got {value}."
- •Use f-strings for interpolation
- •Always assign the message to a
messagevariable before passing toconsole.error()orraise
Comments
Inline comments
- •Use third person imperative ("Configures..." not "This section configures...")
- •Place above the code, not at end of line (unless very short)
- •Use comments to explain non-obvious logic or provide context
# The constant 2.046392675 is the theoretical injectivity bound for 2D cubic B-splines. limit = (1.0 / 2.046392675) * self._grid_sampling * factor
What to avoid
- •Don't reiterate the obvious (e.g.,
# Set x to 5beforex = 5) - •Don't add docstrings/comments to code you didn't write or modify
- •Don't add type annotations as comments (use actual type hints)
- •Don't use heavy section separator blocks (e.g.,
# ======or# ------)
Imports
- •All imports must be at the top of the file. Deferred or inline imports are not allowed.
- •Import sorting and grouping is enforced by ruff. Do not manually reorder imports.
Local import rules
All local (within-library) imports must directly import the required names:
# Good - import specific names from .spline_grid import SplineGrid from .deformation import Deformation, zoom, diffuse # Bad - importing the module itself from . import spline_grid
Cross-package vs within-package imports
Cross-package imports must go through the package's __init__.py:
# Good - imports from the package's __init__.py from ..configuration import RuntimeContext, SingleDayConfiguration # Bad - imports directly from a submodule in another package from ..configuration.single_day import RuntimeContext, SingleDayConfiguration
Within-package imports use direct module imports:
from .spline_grid import SplineGrid
__init__.py conventions
Package __init__.py files define the public API:
"""Provides assets for processing and analyzing neural imaging data.
See the `documentation <https://project-api-docs.netlify.app/>`_ for the description of
available assets. See the `source code repository <https://github.com/Sun-Lab-NBB/project-name>`_
for more details.
Authors: Author Name (Handle)
"""
from .module_one import ClassOne, function_one
from .module_two import ClassTwo, ClassThree
# Optional, per-library choice:
# from ataraxis_base_utilities import console
#
# if not console.enabled:
# console.enable()
__all__ = [
"ClassOne",
"ClassThree",
"ClassTwo",
"function_one",
]
Rules
- •Module docstring: The first line MUST be the bare project description — the same sentence
used in all other canonical description locations (
pyproject.toml,welcome.rst,README.md) with no language or project name prefix. Include documentation link, source repository link, and authors. Email addresses in theAuthors:line are optional and omitted by default - •Console initialization: Enabling the global
consolein the top-level__init__.pyis a per-library choice, not a strict requirement. Ifconsole.echo()is called elsewhere in the library withoutconsole.enable()present, verify with the user whether this is intentional - •Explicit
__all__: Every__init__.pymust declare__all__with all public API members - •Alphabetical sorting: Sort
__all__entries alphabetically - •No logic:
__init__.pyfiles contain only imports, optionalconsole.enable(), and__all__
File-level ordering
All definitions within a file follow this vertical ordering from top to bottom:
- •Module docstring
- •Imports
- •Constants (module-level
_UPPER_SNAKE_CASEvalues) - •Enumerations and dataclasses (type definitions that other code depends on)
- •Public functions and classes (no prefix)
- •Private functions and classes (
_prefixed)
Visibility ordering
Public definitions appear above private definitions. This matches the C-family convention used across all Sun Lab projects (C#, C++), where the public API is presented first and implementation details follow. Readers see the interface before the helpers that support it.
Call-hierarchy ordering
Within each visibility group, definitions should loosely follow the order in which they are called during the library's runtime. When there is no clear call hierarchy, group definitions by purpose.
Enumerations and dataclasses first
Enumerations and dataclasses define the types that worker functions and classes operate on. They must appear above the functions and classes that use them.
Exception — dataclass-only modules: In files whose primary product is the dataclasses themselves, the order is: enumerations first, then public helper functions, then private helper functions, then dataclasses at the bottom.
Boolean expressions
Use truthiness checks instead of explicit comparisons to True or False:
# Good - truthiness
if not self._is_enabled:
return
if items:
process(items=items)
if not file_list:
console.error(message="No files found.", error=FileNotFoundError)
# Avoid - explicit boolean comparison
if self._is_enabled == True: # Wrong
if self._is_enabled is True: # Wrong
if len(items) > 0: # Wrong - use truthiness instead
Exception: Always use is None / is not None for None checks, never truthiness:
# Good - explicit None check
if self._data is not None:
process(data=self._data)
Guard clauses
Prefer early returns (guard clauses) over deeply nested conditionals:
# Good - guard clauses reduce nesting
def process_session(self, data: NDArray[np.float32], threshold: float) -> NDArray[np.float32]:
"""Processes session data with the given threshold."""
if not self._is_enabled:
return data
if data.size == 0:
message = "Unable to process session data. The data array is empty."
console.error(message=message, error=ValueError)
# Main logic at minimal indentation level.
filtered = data[data > threshold]
return filtered
Blank lines
- •Two blank lines between top-level definitions (classes, functions)
- •One blank line between method definitions within a class
- •No blank line after a
defline before the docstring - •One blank line after import blocks before code
Line length and formatting
- •Maximum line length: 120 characters
- •Break long function calls across multiple lines with trailing commas
- •Use parentheses for multi-line strings in error messages
String formatting
- •F-strings only: Always use f-strings for string interpolation. No
%formatting or.format(). - •F-string consistency: When any line requires interpolation, use the
fprefix on all lines of the multi-line string. - •Double quotes: All strings must use double quotes (enforced by ruff). Single quotes are only acceptable inside f-string expressions.
Trailing commas
- •Always use trailing commas when the closing bracket is on a separate line
- •Do not use trailing commas when everything is on one line
Pathlib
Use pathlib.Path for all path manipulation instead of string operations:
config_path = Path(base_directory) / "config" / "settings.yaml"
Related skills
| Skill | Relationship |
|---|---|
/explore-dependencies | Provides live ataraxis dependency API snapshots; invoke before using features |
/cpp-style | Provides C++ conventions; Python conventions parallel these |
/csharp-style | Provides C# conventions; Python conventions parallel these |
/readme-style | Provides README conventions; invoke for README tasks |
/commit | Provides commit message conventions; invoke for commit tasks |
/skill-design | Provides skill file conventions; invoke for skill authoring tasks |
/explore-codebase | Provides project context that informs style-compliant code changes |
Proactive behavior
When reviewing or modifying code, proactively check for style violations and fix them. When writing new code, apply all conventions from this skill and its references without being asked. If you notice existing code near your changes that violates conventions, mention it to the user but do not fix it unless asked.
Verification checklist
You MUST verify your edits against this checklist before submitting any changes to Python files.
Python Style Compliance:
- [ ] Google-style docstrings on all public and private members
- [ ] Docstring section order: Summary -> Extended Description -> Notes -> Args -> Returns -> Raises
- [ ] No Examples sections or in-code examples in docstrings
- [ ] Imperative mood in summaries ("Processes..." not "This method processes...")
- [ ] Prose used instead of bullet lists in docstrings
- [ ] All parameters and returns have type annotations
- [ ] NumPy arrays specify dtype explicitly (NDArray[np.float32])
- [ ] Type aliases use PEP 695 `type` statement syntax
- [ ] Full words used (no abbreviations like `pos`, `idx`, `val`)
- [ ] Private members use `_underscore` prefix
- [ ] Keyword arguments used for function calls
- [ ] Error handling uses console.error() when ataraxis-base-utilities is available (else raise)
- [ ] Double quotes used for all strings (enforced by ruff)
- [ ] F-strings used exclusively (no % formatting or .format())
- [ ] Lines under 120 characters
- [ ] All imports at top of file (no deferred or inline imports)
- [ ] Import sorting delegated to ruff (do not manually reorder)
- [ ] Local imports use direct name imports (no module imports)
- [ ] Cross-package imports go through package __init__.py (not submodules)
- [ ] __init__.py files have __all__ (alphabetically sorted); console.enable() is a per-library choice
- [ ] If console.echo() is used without console.enable() anywhere, verify intent with user
- [ ] Public definitions above private definitions in file
- [ ] Enums and dataclasses above worker functions and classes
- [ ] Definitions ordered by call hierarchy or grouped by purpose
- [ ] Inline comments use third person imperative
- [ ] No heavy section separator blocks (# ====== or # ------)
- [ ] Numba functions use cache=True
- [ ] Decorator stacking order: @staticmethod/@classmethod, @njit, custom, @property
- [ ] Dataclasses use frozen=True for immutable configs (omit for mutable state)
- [ ] Enum members have inline docstrings; StrEnum for strings, IntEnum for codes
- [ ] __repr__ uses ClassName(key=value) format; no __str__
- [ ] Boolean checks use truthiness (not == True); None checks use `is None`
- [ ] Guard clauses / early returns preferred over deep nesting
- [ ] I/O operations separated from processing logic
- [ ] Context managers used for resource management
- [ ] Pathlib used for path manipulation (not string concatenation)
- [ ] Two blank lines between top-level definitions
- [ ] Trailing commas in multi-line structures
Ataraxis Library Preferences (when ataraxis libraries are dependencies):
- [ ] Invoked /explore-dependencies to obtain current API snapshot for each ataraxis dependency
- [ ] Used ataraxis library features instead of standard library equivalents where available
- [ ] Console output uses console.echo() instead of print(); raw=True for pre-formatted content
- [ ] Error handling uses console.error() instead of raise (when ataraxis-base-utilities available)
- [ ] console.enable() presence verified if console.echo() is used in the library