Python Coding Style Guide
Introduction
This style guide provides the coding conventions for Python projects at OpenRig. The goal is to improve code readability and maintain consistency across the entire codebase.
PEP 8 – Style Guide for Python Code for code style
PEP 257 – Docstring Conventions for documentation strings (docstrings).
PEP 484 – Type Hints for type annotations
PEP 282 – A Logging System for logging, and Google’s conventions.
1. Code Layout & Imports (PEP 8)
1.1. Indentation
Use 4 spaces per indentation level. Do not use tabs.
1.2. Maximum Line Length
Limit all lines to a maximum of 88 characters (Ruff default) or 79 characters (strict PEP 8).
1.3. Blank Lines
Surround top-level function and class definitions with two blank lines.
Method definitions inside a class are surrounded by a single blank line.
1.4. Imports
Imports should be grouped in the following order:
Standard library imports.
Related third-party imports (e.g.,
numpy,maya).Local application/library specific imports.
Put a blank line between each group of imports.
Yes:
import os import sys import maya.cmds as cmds from openrig.core import base
No:
import maya.cmds as cmds import os from openrig.core import base import sys
2. Naming (PEP 8)
Choosing sensible names is crucial. Follow these conventions to maintain clarity.
2.1. Variables
Variable names should be in snake_case (lowercase with underscores).
Yes:
user_profile = get_user_profile() max_retries = 5
No:
userProfile = get_user_profile() # Avoid camelCase MaxRetries = 5 # Avoid PascalCase
2.2. Functions and Methods
Like variables, functions and methods should be in snake_case.
Yes:
def calculate_total_value(quantity, value): # ... class User: def get_full_name(self): # ...
No:
def CalculateTotalValue(quantity, value): # Avoid PascalCase # ... class User: def getFullName(self): # Avoid camelCase # ...
2.3. Classes
Class names should follow the PascalCase convention (also known as CapWords).
Yes:
class OpenRigAgent: # ... class DiagramGenerateRequest(BaseModel): # ...
No:
class open_rig_agent: # Avoid snake_case # ...
2.4. Modules
Python module names should be short, lowercase, and, if necessary, use underscores to improve readability.
Yes:
# File: src/openrig/utils/string_helpers.py import string_helpers
No:
# File: src/openrig/utils/StringHelpers.py import StringHelpers # Avoid PascalCase
2.5. Constants
Constants are declared at the module level and written in all capital letters with underscores separating words.
Yes:
# In a module DEFAULT_MODEL = "gpt-4" MAX_WORKERS = 10
No:
default_model = "gpt-4" # Avoid snake_case for constants
3. Docstrings (Google Style & PEP 257)
All public modules, functions, classes, and methods must have docstrings.
3.1. General Guidelines (PEP 257)
The docstring is a string literal that appears as the first statement in the definition.
Use
"""triple double quotes"""for docstrings.The docstring should be concise and describe what the object does, not how it does it.
The first line should be a short, imperative summary ending in a period.
3.2. Google Style for Functions and Methods
Function and method docstrings should contain:
A one-line summary ending in a period.
A more detailed description (optional, but recommended for complex functions). This section can explain behavior, algorithms, or side effects.
An
Args:section to describe the arguments.A
Returns:section to describe the return value.An optional
Raises:section to describe any exceptions that are raised.
Complete Example:
def build_name(self, **kwargs: object) -> str: """Builds a validated name from the given token values. Each keyword argument corresponds to a token defined in the active convention. Values are normalized before validation, so raw inputs such as ``"upper_arm"`` or ``Side.LEFT`` are accepted. Args: **kwargs: Token name/value pairs (e.g. ``descriptor="arm"``, ``side="l"``, ``usage="jnt"``). Returns: A fully validated name string built from the provided tokens. Raises: NamingValidationError: If any token value fails validation after normalization, or if the assembled name violates a global rule. """ ...
Example with
Raises:def _require_str(data: dict, key: str) -> str: """Extracts a required string value from a configuration dict. Args: data: The configuration dictionary to read from. key: The key whose value must be a non-empty string. Returns: The string value associated with *key*. Raises: NamingConfigError: If *key* is missing or its value is not a string. """ value = data.get(key) if not isinstance(value, str): raise NamingConfigError(f"Expected string for '{key}', got {type(value)}") return value
3.3. Google Style for Classes
Class docstrings should contain:
A one-line summary.
A more detailed description of the class’s purpose.
An
Attributes:(orArgs:in__init__) section to describe public attributes.An optional
Example:block showing basic usage.
Class Example:
class Manager: """Validates, parses, and builds names according to a configurable convention. A ``Manager`` is built from a set of ordered tokens, a separator, per-token rules, and optional normalizers. It can validate raw names, extract token values, and assemble new names with automatic normalization. Attributes: tokens: Ordered list of token names that form a valid name. separator: The string used to join tokens (default ``"_"``). """ def __init__( self, tokens: list[str], separator: str, rules: dict[str, RuleValidator], global_rules: GlobalRules | None = None, ) -> None: """Initializes the Manager with a naming convention. Args: tokens: Ordered token names (e.g. ``["descriptor", "side", "usage"]``). separator: Character(s) used to join tokens. rules: Mapping from token name to its validation rule. global_rules: Optional global constraints applied to the full name. """ ...
Example with
Example:class RegexRule: """Validates a token value against a regular expression. Attributes: pattern: The compiled regex pattern used for validation. Example: rule = RegexRule(pattern=r"^[a-z][a-zA-Z0-9]*$") rule.validate("upperArm") # True rule.validate("UpperArm") # False """ pattern: re.Pattern
3.4. Module Docstrings
Module docstrings should be at the top of the file and summarize the module’s content and purpose.
Example:
"""Normalizer functions for converting raw token values to their canonical form. Each function accepts any object as input and always returns a ``str``. Falsy inputs (``None``, ``""``, ``0``) return an empty string. Normalizers are assigned per-token in ``naming/config.json`` and applied automatically by the ``Manager`` before validation. """ # ... rest of the module's code ...
4. Type Hinting (PEP 484)
Type hints improve code readability and enable static analysis. We follow PEP 484 standards.
4.1. Function Signatures
All functions and methods must include type annotations for arguments and return values.
Yes:
def calculate_offset(value: float, bias: float = 0.0) -> float: return value + bias
No:
def calculate_offset(value, bias=0.0): return value + bias
4.2. Complex Types
Use generic types from the typing module (or built-ins in Python 3.9+) for complex structures.
Yes:
def process_items(items: list[str]) -> dict[str, int]: # ...
5. Logging (PEP 282)
We follow PEP 282 standards for logging.
5.1. Usage
Use the standard logging module instead of print() for tracking events, status, and errors.
Yes:
import logging logger = logging.getLogger(__name__) logger.info("Operation started")
No:
print("Operation started")
5.2. Log Levels
Select the appropriate level: DEBUG, INFO, WARNING, ERROR, or CRITICAL.
6. Tooling
To ensure consistency and automate compliance, use the following tools:
Linter + Formatter: Ruff — handles code style, import sorting, and docstring conventions in a single tool. Configured in
pyproject.toml.