Skip to content

Code Style

Python Version

JAFF supports Python 3.9 and higher. Write code that is compatible with Python 3.9+.

# Good - use modern type hints
from typing import List, Dict, Optional

def process(items: List[str]) -> Dict[str, int]:
    pass

# Avoid - Python 3.8 syntax
def process(items: list[str]) -> dict[str, int]:  # Requires 3.9+
    pass

Code Formatting

Use Ruff for both formatting and linting, replacing Black entirely.

# Format all code
ruff format src/ tests/

# Check formatting without modifying (Black --check equivalent)
ruff format --check src/

# Format specific file
ruff format src/jaff/network.py
## Code Formatting

Naming Conventions

Variables and Functions

Use snake_case:

# Good
user_name = "Alice"
def calculate_rate(temperature):
    pass

# Avoid
userName = "Alice"
def CalculateRate(temperature):
    pass

Classes

Use PascalCase:

# Good
class Network:
    pass

class CodeGenerator:
    pass

# Avoid
class network:
    pass

Constants

Use UPPER_SNAKE_CASE:

# Good
MAX_ITERATIONS = 1000
DEFAULT_TEMPERATURE = 300.0

# Avoid
maxIterations = 1000

Private Members

Prefix with underscore:

class Network:
    def __init__(self):
        self._internal_data = []  # Private
        self.public_data = []     # Public

    def _helper_method(self):  # Private
        pass

    def public_method(self):   # Public
        pass

Type Hints

Always Use Type Hints

# Good
def compute_rate(temperature: float, alpha: float) -> float:
    return alpha * temperature

# Avoid
def compute_rate(temperature, alpha):
    return alpha * temperature

Import Types

from typing import List, Dict, Optional, Union, Tuple, Any
from pathlib import Path
import numpy as np

def process_species(
    names: List[str],
    masses: np.ndarray,
    options: Optional[Dict[str, Any]] = None
) -> Tuple[List[str], np.ndarray]:
    pass

Complex Types

from typing import List, Dict, Union, Optional

# Type aliases for clarity
SpeciesDict = Dict[str, int]
RateExpression = Union[str, float]

def load_network(
    filename: str,
    species_map: Optional[SpeciesDict] = None
) -> Network:
    pass

Docstrings

Google Style

Use Google-style docstrings:

def compute_rates(network: Network, temperature: float) -> np.ndarray:
    """Compute reaction rate coefficients.

    This function calculates rate coefficients for all reactions
    in the network at the specified temperature.

    Args:
        network: Chemical reaction network
        temperature: Gas temperature in Kelvin

    Returns:
        Array of rate coefficients in cm³/s

    Raises:
        ValueError: If temperature is negative

    Example:
        >>> net = Network("network.dat")
        >>> rates = compute_rates(net, 100.0)
        >>> print(rates[0])
        1.2e-10
    """
    if temperature < 0:
        raise ValueError("Temperature must be non-negative")
    return network.calculate_rates(temperature)

Class Docstrings

class Codegen:
    """Multi-language code generator for chemical networks.

    This class generates optimized code for evaluating reaction rates,
    ODEs, and Jacobians in multiple programming languages.

    Attributes:
        net: Chemical reaction network
        lang: Target programming language
        ioff: Array indexing offset (0 or 1)

    Example:
        >>> net = Network("network.dat")
        >>> cg = Codegen(network=net, lang="c++")
        >>> rates = cg.get_rates(use_cse=True)
    """

    def __init__(self, network: Network, lang: str = "c++"):
        """Initialize code generator.

        Args:
            network: Chemical reaction network
            lang: Target language (c++, c, fortran, python)
        """
        pass

Module Docstrings

"""Chemical reaction network module.

This module provides the Network class for loading and managing
chemical reaction networks from various file formats.

Example:
    >>> from jaff import Network
    >>> net = Network("network.dat")
"""

Error Handling

Specific Exceptions

# Good
try:
    net = Network(filename)
except FileNotFoundError:
    print(f"File not found: {filename}")
except ValueError as e:
    print(f"Invalid network format: {e}")

# Avoid
try:
    net = Network(filename)
except:  # Too broad
    pass

Custom Exceptions

class NetworkError(Exception):
    """Base exception for network errors."""
    pass

class ParseError(NetworkError):
    """Error parsing network file."""
    pass

def load_network(filename: str) -> Network:
    try:
        return Network(filename)
    except ValueError as e:
        raise ParseError(f"Failed to parse {filename}: {e}")

Error Messages

# Good - descriptive
raise ValueError(f"Temperature must be positive, got {temp}")

# Avoid - vague
raise ValueError("Bad temperature")

Testing Style

Test Function Names

# Good - descriptive
def test_network_loads_krome_format():
    pass

def test_codegen_raises_error_for_invalid_language():
    pass

# Avoid
def test1():
    pass

Test Organization

class TestNetwork:
    """Tests for Network class."""

    def test_load_from_file(self):
        """Test loading network from file."""
        pass

    def test_validate_species(self):
        """Test species validation."""
        pass

Assertions

# Good - clear assertions
assert len(net.species) == 35
assert net.label == "react_COthin"
assert_almost_equal(rate, 1.2e-10, decimal=15)

# Avoid - unclear
assert x

Performance

List Comprehensions

# Good - fast
species_names = [s.name for s in network.species]

# Slower
species_names = []
for s in network.species:
    species_names.append(s.name)

String Formatting

# Good - f-strings (fast, readable)
message = f"Network has {n} species"

# Avoid - slow
message = "Network has " + str(n) + " species"

Best Practices

Use Context Managers

# Good
with open(filename, 'w') as f:
    f.write(content)

# Avoid
f = open(filename, 'w')
f.write(content)
f.close()

Use Pathlib

from pathlib import Path

# Good
path = Path("networks") / "react.dat"
if path.exists():
    content = path.read_text()

# Avoid
import os
path = os.path.join("networks", "react.dat")
if os.path.exists(path):
    with open(path) as f:
        content = f.read()

Early Returns

# Good - early return
def process(value: Optional[int]) -> int:
    if value is None:
        return 0

    if value < 0:
        return -1

    return value * 2

# Avoid - nested if
def process(value):
    if value is not None:
        if value >= 0:
            return value * 2
        else:
            return -1
    else:
        return 0

Code Review Checklist

Before submitting code:

  • Code formatted with ruff
  • Sort imports with ruff
  • No linting errors (ruff)
  • Type hints added
  • Docstrings written
  • Tests added/updated
  • No commented-out code
  • No hardcoded paths
  • Error handling in place

See Also