Skip to content

Development Style Guide & Project Rules

Status Applies to Owner
Pre-release draft main branch as of 2026-06-30 coding and review rules Development maintainers

1. Core Philosophy

Optimize for explicit, self-documenting code over terse keystroke-saving. Prioritize legibility and mathematical clarity. Engineers should never have to guess what variables represent.

2. Python Guidelines

Tooling & Environment

  • Package Management: Strictly managed by uv.
  • Linting & Formatting: Managed entirely by ruff (ruff format is the source of truth).

Imports

  • Rule: Default to module-qualified imports. Import the module, then access members through its namespace.
  • Good: import scipy.stats; scipy.stats.norm(...)
  • Good: import sklearn.linear_model; sklearn.linear_model.LinearRegression(...)
  • Bad: from scipy.stats import norm
  • Bad: from sklearn.linear_model import LinearRegression
  • Rule: The only approved direct-import exceptions are from pathlib import Path and from dataclasses import dataclass.
  • Rule: Import typing as a module and qualify all names. Never import from typing.
  • Good: import typing; typing.TYPE_CHECKING; typing.Any
  • Bad: from typing import TYPE_CHECKING, Any
  • Rule: Import enum and collections.abc as modules and qualify all names.
  • Good: import enum; enum.StrEnum
  • Good: import collections.abc; collections.abc.Iterator
  • Bad: from enum import StrEnum
  • Bad: from collections.abc import Iterator
  • Rule: Use conventional aliases only where they are already standard and improve readability.
  • Approved runtime examples: import numpy as np, import numpy.typing as npt, import jax.numpy as jnp
  • Polars may be used in dev scripts or tests only, not runtime src/g code.
  • Rule: Keep imports out of functions, methods, and classes in production code under src/g unless the local import is an explicit runtime boundary.
  • Module-scope imports are the default.
  • if typing.TYPE_CHECKING: blocks are allowed for annotation-only imports.
  • Allowed local imports:
    • runtime boundary imports that intentionally delay JAX or native module initialization
    • optional dependency helpers
    • typing.TYPE_CHECKING annotation-only imports
  • Not allowed: casual local imports to avoid circular dependencies.
  • Tests may use local imports when there is a concrete reason, such as optional dependencies or fixture isolation.
  • Rule: Relative imports are not allowed.
  • Rule: In production Python code under src/g, import first-party modules rather than first-party members.
  • Good: from g import types; types.Device
  • Bad: from g.types import Device
  • Rule: Internal package initializers under src/g/**/__init__.py are package markers only. Do not define __all__, import/re-export submodules, assign aliases, or place helper functions there.
  • Rule: The top-level src/g/__init__.py may keep the lazy public entrypoint boundary required by the console script, but it must not define __all__.
  • Run uv run python -m tooling.cli.debug tool.name=check_internal_init_exports (or just check-internal-init-exports) before reviewing changes that touch package initializers.

Naming Conventions

  • Rule: No abbreviations. No single-letter math variables. Use full, descriptive words.
  • Bad: X, y, var, log_var, maf
  • Good: design_matrix (or features), phenotypes (or targets), variance, log_variance, minor_allele_frequency
  • Rule: No leading underscores for function or method names. We do not use pseudo-private indicators.
  • Bad: def _calculate_variance():
  • Good: def calculate_variance():

Type Annotations

  • Rule: 100% Type Annotation Coverage.
  • Types must pass the ty type checker without implicit Any fallbacks. Use exact types (e.g., jax.Array, np.ndarray).
  • Rule: Finite sets of string values must use enum.StrEnum, not str annotations, Literal[...], or ad-hoc validation sets. This applies to configuration values, modes, formats, codecs, CLI choices, and any other closed choice domain.
  • Rule: Define an enum in the narrowest valid scope. If it is used in exactly one production file, define it in that file. If it is used in more than one production file, define it in src/g/types.py.
  • Rule: Test-only enums should not live in production modules. If a test-only enum is used in one test file, define it in that test file. If it is shared across multiple test files, define it in tests/types.py.

Return Types & Structured Data

  • Rule: Never return bare tuples for multiple values. Use @dataclass(frozen=True).
    Bad:
    def compute_regression(features: jax.Array, targets: jax.Array) -> tuple[jax.Array, jax.Array]:
    return betas, standard_errors

Good:
from dataclasses import dataclass
import jax

@dataclass(frozen=True)
class RegressionResult:
"""Result of regression computation.

  Attributes:
      betas: Coefficient estimates.
      standard\_errors: Standard errors of estimates.

  """  
  betas: jax.Array  
  standard\_errors: jax.Array

def compute_regression(features: jax.Array, targets: jax.Array) -> RegressionResult:
return RegressionResult(betas=betas, standard_errors=standard_errors)

JAX Pytree Containers

Internal Defaults

  • Rule: Production Python code under src/g must not define default values on function or method parameters.
  • Rule: Dataclasses under src/g must not define field defaults or default_factory values.
  • Pass optional values explicitly as None, and pass policy constants explicitly at the call site. Defaults belong at config parsing, native boundary adaptation, or test/tooling helpers, not inside internal runtime code.
  • Run uv run python -m tooling.cli.debug tool.name=check_internal_defaults (or just check-internal-defaults) before reviewing changes that touch internal signatures or dataclasses.

  • Rule: For containers used inside JAX JIT boundaries (state containers, loop carriers), use @dataclass(frozen=True) with @jax.tree_util.register_dataclass.

Good (for JIT-internal state):
from dataclasses import dataclass
import jax

@jax.tree_util.register_dataclass
@dataclass(frozen=True)
class LogisticState:
"""State for batched logistic IRLS.

  Attributes:
      coefficients: Current coefficient estimates.
      converged\_mask: Boolean convergence mask.
      iteration\_count: Iteration counter.

  """  
  coefficients: jax.Array  
  converged\_mask: jax.Array  
  iteration\_count: jax.Array
  • Rule: For containers with mixed types (strings, booleans, numpy arrays alongside JAX arrays) that are NOT passed through JIT boundaries, use @dataclass(frozen=True) without JAX registration.

Good (for I/O and metadata containers):
from dataclasses import dataclass
import jax
import numpy.typing as npt
import numpy as np

@dataclass(frozen=True)
class GenotypeChunk:
"""Genotype matrix with mixed metadata.

  Attributes:
      genotypes: JAX array of genotypes.
      has\_missing\_values: Python boolean flag.
      allele\_frequency: JAX array of frequencies.

  """  
  genotypes: jax.Array  
  has\_missing\_values: bool  
  allele\_frequency: jax.Array
  • Note on NamedTuple: Avoid typing.NamedTuple. While it supports unpacking and indexing, @dataclass(frozen=True) provides better consistency, IDE support, and explicit immutability. The codebase standardizes on dataclasses for all structured return types

Documentation

  • Use the Google Python Style Guide format for docstrings.
  • Rule: Omit types in docstrings. Since types are strictly enforced in the function signature, duplicating them in the docstring creates maintenance overhead.
  • Detail Args:, Returns:, and Raises: with descriptions only.

PyO3 Native Stub Maintenance

  • Follow Binding Layer Policy for all code under src/binding.
  • Keep src/g/_core.pyi aligned with the g._core.cli registrations under src/binding/.
  • Update the stub whenever Rust #[pyclass], #[pyfunction], add_class, or add_function changes touch argument or return types used by Python callers.
  • Run uv run python -m tooling.cli.debug tool.name=check_pyo3_stub (or just check-core-stub) before reviewing Rust/native-facing type updates.
  • Treat just check failures from check-core-stub as mandatory follow-up work when modifying native API exports.