diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f349ee5..cc7e5cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,6 @@ jobs: matrix: target: - black - - dapperdata - mypy - pytest - ruff diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d6d93e7..f64abe1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,31 +1,4 @@ repos: - - repo: local - hooks: - - id: pytest - name: pytest - entry: make test/pytest - language: system - pass_filenames: false - - id: ruff - name: ruff - entry: make test/ruff - language: system - pass_filenames: false - - id: black - name: black - entry: make test/black - language: system - pass_filenames: false - - id: mypy - name: mypy - entry: make test/mypy - language: system - pass_filenames: false - - id: tomlsort - name: tomlsort - entry: make test/tomlsort - language: system - pass_filenames: false - repo: https://github.com/pre-commit/pre-commit-hooks rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0 , 9 Aug 2025 hooks: @@ -34,14 +7,36 @@ repos: - id: check-case-conflict - id: check-merge-conflict - id: check-shebang-scripts-are-executable + exclude: '\.\w+$' - id: end-of-file-fixer - exclude: '\.(diff|patch)$' + exclude: '\.(diff|patch|py)$' - id: trailing-whitespace - exclude: '\.(diff|patch)$' + exclude: '\.(diff|patch|py)$' - id: check-json - id: check-yaml + - repo: https://github.com/pappasam/toml-sort + rev: 2970ae9bb7124fe5117a27e10c10d2da051ce05a # v0.24.4, 24 Mar 2026 + hooks: + - id: toml-sort-fix + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.13 # 14 May 2026 (immutable) + hooks: + - id: ruff-check # Linting + args: [ --fix ] + - id: ruff-format # Formatting + ci: + autofix_commit_msg: | + Apply fixes from pre-commit hooks: see detailed commit message ↴ + + This commit means that you must (please!) install pre-commit + on your development machine and run `pre-commit install --install-hooks`. + For more information, see + https://celeritas-project.github.io/celeritas/user/development/style.html#formatting + + Autogenerated: https://pre-commit.ci autoupdate_schedule: quarterly autoupdate_commit_msg: "Update pre-commit version" diff --git a/celerpy/cli.py b/celerpy/cli.py index 9bb5cf4..50a48c2 100644 --- a/celerpy/cli.py +++ b/celerpy/cli.py @@ -1,7 +1,7 @@ # Copyright 2024 UT-Battelle, LLC, and other Celeritas developers. # See the top-level LICENSE file for details. # SPDX-License-Identifier: Apache-2.0 -from typing import Annotated, Optional +from typing import Annotated import typer from pydantic import ValidationError @@ -31,7 +31,7 @@ def print_version(value: bool): @app.command() def main( version: Annotated[ - Optional[bool], + bool | None, typer.Option("--version", callback=print_version, is_eager=True), ] = None, ): diff --git a/celerpy/conf/settings.py b/celerpy/conf/settings.py index a98d517..8537629 100644 --- a/celerpy/conf/settings.py +++ b/celerpy/conf/settings.py @@ -1,7 +1,6 @@ # Copyright 2024 UT-Battelle, LLC, and other Celeritas developers. # See the top-level LICENSE file for details. # SPDX-License-Identifier: Apache-2.0 -from typing import Optional from pydantic import DirectoryPath, FilePath from pydantic_settings import BaseSettings, SettingsConfigDict @@ -24,7 +23,7 @@ class Settings(BaseSettings): use_attribute_docstrings=True, ) - prefix_path: Optional[DirectoryPath] = None + prefix_path: DirectoryPath | None = None "Path to the Celeritas build/install directory" # CELER_ environment variables @@ -46,7 +45,7 @@ class Settings(BaseSettings): # Geant4->ORANGE conversion - g4org_options: Optional[FilePath] = None + g4org_options: FilePath | None = None "JSON file with conversion options" # Geant4 configuration diff --git a/celerpy/model/input.py b/celerpy/model/input.py index d17b6e1..68bed15 100644 --- a/celerpy/model/input.py +++ b/celerpy/model/input.py @@ -4,7 +4,7 @@ """Input models for commands and configuration with Celeritas.""" from enum import StrEnum, auto -from typing import Literal, Optional +from typing import Literal from pydantic import FilePath, NonNegativeInt, PositiveFloat, PositiveInt from pydantic_core import to_json @@ -38,7 +38,7 @@ class OrangeGeoFromCsg(_Model): unit_length: PositiveFloat = 1.0 "Scale factor (input unit length), customizable for unit testing" - tol: Optional[Tolerance] = None + tol: Tolerance | None = None "Construction and tracking tolerance (native units)" # Structural conversion @@ -51,13 +51,13 @@ class OrangeGeoFromCsg(_Model): # Debug output - objects_output_file: Optional[str] = None + objects_output_file: str | None = None "Write converted Geant4 object structure to a JSON file" - csg_output_file: Optional[str] = None + csg_output_file: str | None = None "Write constructed CSG surfaces and tree to a JSON file" - org_output_file: Optional[str] = None + org_output_file: str | None = None "Write final org.json to a JSON file" @@ -95,13 +95,13 @@ class OrangeGeoFromGeant(OrangeGeoFromCsg): # celer-geo/GeoInput.hh class ModelSetup(_Model): - cuda_stack_size: Optional[NonNegativeInt] = None - cuda_heap_size: Optional[NonNegativeInt] = None + cuda_stack_size: NonNegativeInt | None = None + cuda_heap_size: NonNegativeInt | None = None geometry_file: FilePath "Path to the GDML input file" - perfetto_file: Optional[FilePath] = None + perfetto_file: FilePath | None = None "Path to write Perfetto profiling output" @@ -110,10 +110,10 @@ class TraceSetup(_Model): _cmd: Literal["trace"] = "trace" "Command name in the JSON file" - geometry: Optional[GeometryEngine] = None + geometry: GeometryEngine | None = None "Geometry engine with which to perform the trace" - memspace: Optional[MemSpace] = None + memspace: MemSpace | None = None "Whether to perform the trace on CPU or GPU" volumes: bool = True @@ -137,13 +137,13 @@ class ImageInput(_Model): vertical_pixels: NonNegativeInt = 512 "Number of pixels along the y axis" - horizontal_divisor: Optional[PositiveInt] = None + horizontal_divisor: PositiveInt | None = None "Increase the horizontal window to be divisible by this number" # ad hoc: input to a 'trace' command class TraceInput(TraceSetup): - image: Optional[ImageInput] = None + image: ImageInput | None = None "Reuse the existing image" diff --git a/celerpy/model/output.py b/celerpy/model/output.py index 2f08d5c..eb4ef0c 100644 --- a/celerpy/model/output.py +++ b/celerpy/model/output.py @@ -38,7 +38,7 @@ class ImageParams(_Model): class TraceOutput(_Model): trace: TraceSetup image: ImageParams - volumes: Optional[list[str]] = None + volumes: list[str] | None = None sizeof_int: PositiveInt @@ -140,8 +140,8 @@ class ExceptionDump(_Model): context: Optional["ExceptionDump"] = None # corecel/AssertIO.json.cc - what: Optional[str] + what: str | None which: str - condition: Optional[str] = None - file: Optional[str] = None - line: Optional[int] = None + condition: str | None = None + file: str | None = None + line: int | None = None diff --git a/celerpy/process.py b/celerpy/process.py index eb5c819..10b65fa 100644 --- a/celerpy/process.py +++ b/celerpy/process.py @@ -7,7 +7,7 @@ import os from signal import SIGINT, SIGKILL, SIGTERM from subprocess import PIPE, Popen, TimeoutExpired -from typing import Optional, TypeVar +from typing import TypeVar from pydantic import BaseModel, ValidationError @@ -81,7 +81,7 @@ def close(process: P, *, timeout: float = 0.1): return out -def communicate(process: P, line: str) -> Optional[str]: +def communicate(process: P, line: str) -> str | None: """Write a line and read a line of response. For this to work, the child application *must* write a single line of diff --git a/celerpy/visualize.py b/celerpy/visualize.py index 801fd16..e67b8fe 100644 --- a/celerpy/visualize.py +++ b/celerpy/visualize.py @@ -12,7 +12,7 @@ from pathlib import Path from subprocess import TimeoutExpired from tempfile import NamedTemporaryFile -from typing import Any, NamedTuple, Optional, Union +from typing import Any, NamedTuple import matplotlib.pyplot as plt import numpy as np @@ -126,7 +126,7 @@ class CelerGeo: refactor. """ - image: Optional[ImageParams] + image: ImageParams | None volumes: dict[GeometryEngine, list[str]] @classmethod @@ -170,9 +170,9 @@ def reset_id_map(self): def trace( self, - image: Optional[ImageInput] = None, + image: ImageInput | None = None, *, - geometry: Optional[GeometryEngine] = None, + geometry: GeometryEngine | None = None, **kwargs, ): """Trace with a geometry, memspace, etc.""" @@ -216,7 +216,7 @@ def trace( return (result, npimg) - def close(self, *, timeout: float = 0.25) -> Union[dict[str, dict], str]: + def close(self, *, timeout: float = 0.25) -> dict[str, dict] | str: """Cleanly exit the ray trace loop, returning run statistics if possible. """ @@ -286,7 +286,7 @@ def calc_axes(length, dir): class Imager: - axes: Optional[LabeledAxes] = None + axes: LabeledAxes | None = None def __init__(self, celer_geo: CelerGeo, image: ImageInput): self.celer_geo = celer_geo @@ -296,9 +296,9 @@ def __init__(self, celer_geo: CelerGeo, image: ImageInput): def __call__( self, ax: mpl_Axes, - geometry: Optional[GeometryEngine] = None, - memspace: Optional[MemSpace] = None, - colorbar: Union[bool, None, mpl_Axes] = None, + geometry: GeometryEngine | None = None, + memspace: MemSpace | None = None, + colorbar: bool | None | mpl_Axes = None, ) -> dict[str, Any]: (trace_output, img) = self.celer_geo.trace( self.image, geometry=geometry, memspace=memspace @@ -354,8 +354,8 @@ def plot_all_geometry( trace_image: Imager, *, colorbar: bool = True, - figsize: Optional[tuple] = None, - engines: Optional[Iterable] = None, + figsize: tuple | None = None, + engines: Iterable | None = None, ) -> Mapping[GeometryEngine, Any]: """Convenience function for plotting all available geometry types.""" if engines is None: @@ -376,7 +376,7 @@ def plot_all_geometry( if colorbar: all_cbar[:0] = [all_ax[-1]] - for ax, g, cb in zip(all_ax, engines, all_cbar): + for ax, g, cb in zip(all_ax, engines, all_cbar, strict=True): try: result[g] = trace_image(ax, geometry=g, colorbar=cb) except Exception as e: @@ -387,7 +387,7 @@ def plot_all_geometry( def centered_image( center: ArrayLike = (0, 0, 0), *, - width: Union[float, tuple, np.ndarray], + width: float | tuple | np.ndarray, xdir: ArrayLike = (0, 0, 1), outdir: ArrayLike = (0, 1, 0), **kwargs: Any, diff --git a/test/mock-prefix/bin/celer-geo b/test/mock-prefix/bin/celer-geo index d7925e0..8a09f8a 100755 --- a/test/mock-prefix/bin/celer-geo +++ b/test/mock-prefix/bin/celer-geo @@ -16,11 +16,17 @@ setup_signals() celer_log = environ.get("CELER_LOG") assert celer_log == "debug", f"Expected CELER_LOG=debug, got {celer_log}" celer_log_local = environ.get("CELER_LOG_LOCAL") -assert celer_log_local == "warning", f"Expected CELER_LOG_LOCAL=warning, got {celer_log_local}" +assert celer_log_local == "warning", ( + f"Expected CELER_LOG_LOCAL=warning, got {celer_log_local}" +) g4org_opts = environ.get("G4ORG_OPTIONS") -assert g4org_opts and Path(g4org_opts).is_file(), f"Expected G4ORG_OPTIONS=1, got {g4org_opts}" +assert g4org_opts and Path(g4org_opts).is_file(), ( + f"Expected G4ORG_OPTIONS=1, got {g4org_opts}" +) profiling = environ.get("CELER_ENABLE_PROFILING") -assert profiling == "True", f"Expected CELER_ENABLE_PROFILING=1, got {profiling}" +assert profiling == "True", ( + f"Expected CELER_ENABLE_PROFILING=1, got {profiling}" +) # Read the initial command and echo it (with version) @@ -46,7 +52,7 @@ expect_trace( ) cmd = read_input() -assert cmd == None +assert cmd is None dump( { "runtime": {"device": None, "kernels": [], "version": "0.7.0-dev"}, diff --git a/test/mock-prefix/bin/mock-process b/test/mock-prefix/bin/mock-process index 0d8470b..5ad4b9f 100755 --- a/test/mock-prefix/bin/mock-process +++ b/test/mock-prefix/bin/mock-process @@ -2,19 +2,19 @@ # Copyright 2024 UT-Battelle, LLC, and other Celeritas developers. # See the top-level COPYRIGHT file for details. # SPDX-License-Identifier: (Apache-2.0 OR MIT) -"""Mock a 'JSON lines' style process. +"""Mock a 'JSON lines' style process.""" -""" -from mockutils import log, dump, read_input, setup_signals import sys +from mockutils import dump, log, read_input, setup_signals + setup_signals() cmd = input() log("read command", repr(cmd)) dump("success") log("entering loop") -while (cmd := read_input()) != None: +while (cmd := read_input()) is not None: log("read command", cmd) if cmd == "abort": log("Aborting!")