diff --git a/.copier-answers.yml b/.copier-answers.yml index 3a29eb49..4ffd0e3e 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier; NEVER EDIT MANUALLY -_commit: c721391 +_commit: 91ffffb _src_path: gh:scipp/copier_template description: SANS data reduction for the European Spallation Source max_python: '3.12' diff --git a/.github/workflows/copier.yml b/.github/workflows/copier.yml deleted file mode 100644 index b1fe6ce4..00000000 --- a/.github/workflows/copier.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Copier template sync - -on: - workflow_dispatch: - schedule: - - cron: '0 1 * * 1' - -jobs: - update: - name: Sync with copier template - runs-on: 'ubuntu-latest' - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.X' - - run: pip install copier - - run: copier update --skip-answered --vcs-ref=HEAD --conflict=rej - - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 - with: - title: '[CRON] Sync with copier template' - body: | - This PR updates the project to the latest version of scipp's [copier-template](https://github.com/scipp/copier_template). - Remember to check for any conflicts and resolve them. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ab1be29a..98aaf568 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -65,7 +65,7 @@ jobs: name: docs_html path: html/ - - uses: JamesIves/github-pages-deploy-action@v4.5.0 + - uses: JamesIves/github-pages-deploy-action@v4.6.1 if: ${{ inputs.publish }} with: branch: gh-pages diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19830db7..4442b1b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,15 +13,6 @@ repos: - id: trailing-whitespace args: [ --markdown-linebreak-ext=md ] exclude: '\.svg' - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort (python) - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.11.0 - hooks: - - id: black - repo: https://github.com/kynan/nbstripout rev: 0.6.0 hooks: @@ -29,18 +20,14 @@ repos: types: [ "jupyter" ] args: [ "--drop-empty-cells", "--extra-keys 'metadata.language_info.version cell.metadata.jp-MarkdownHeadingCollapsed cell.metadata.pycharm'" ] - - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - types: ["python"] - additional_dependencies: ["flake8-bugbear==23.9.16"] - - repo: https://github.com/pycqa/bandit - rev: 1.7.5 - hooks: - - id: bandit - additional_dependencies: ["bandit[toml]"] - args: ["-c", "pyproject.toml"] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.3 + hooks: + - id: ruff + args: [ --fix ] + types_or: [ python, pyi, jupyter ] + - id: ruff-format + types_or: [ python, pyi ] - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: diff --git a/docs/_templates/doc_version.html b/docs/_templates/doc_version.html index 48f9aacf..395dc60a 100644 --- a/docs/_templates/doc_version.html +++ b/docs/_templates/doc_version.html @@ -1,2 +1,2 @@ -Current {{ project }} version: {{ version }} (older versions). +Current ESSsans version: {{ version }} (older versions). diff --git a/docs/conf.py b/docs/conf.py index d9579e30..e5d679b0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,16 +1,19 @@ -# -*- coding: utf-8 -*- - import doctest import os import sys +from importlib.metadata import PackageNotFoundError from importlib.metadata import version as get_version +from sphinx.util import logging + sys.path.insert(0, os.path.abspath('.')) +logger = logging.getLogger(__name__) + # General information about the project. -project = u'ESSsans' -copyright = u'2024 Scipp contributors' -author = u'Scipp contributors' +project = 'ESSsans' +copyright = '2024 Scipp contributors' +author = 'Scipp contributors' html_show_sourcelink = True @@ -34,6 +37,8 @@ import sciline.sphinxext.domain_types # noqa: F401 extensions.append('sciline.sphinxext.domain_types') + # See https://github.com/tox-dev/sphinx-autodoc-typehints/issues/457 + suppress_warnings = ["config.cache"] except ModuleNotFoundError: pass @@ -110,8 +115,15 @@ # built documents. # -release = get_version("esssans") -version = ".".join(release.split('.')[:3]) # CalVer +try: + release = get_version("esssans") + version = ".".join(release.split('.')[:3]) # CalVer +except PackageNotFoundError: + logger.info( + "Warning: determining version from package metadata failed, falling back to " + "a dummy version number." + ) + release = version = "0.0.0-dev" warning_is_error = True diff --git a/docs/developer/coding-conventions.md b/docs/developer/coding-conventions.md index b23c0eb4..4fafc18d 100644 --- a/docs/developer/coding-conventions.md +++ b/docs/developer/coding-conventions.md @@ -2,7 +2,7 @@ ## Code formatting -There are no explicit code formatting conventions since we use `black` to enforce a format. +There are no explicit code formatting conventions since we use `ruff` to enforce a format. ## Docstring format diff --git a/docs/user-guide/common/beam-center-finder.ipynb b/docs/user-guide/common/beam-center-finder.ipynb index 50e69f74..379f9c64 100644 --- a/docs/user-guide/common/beam-center-finder.ipynb +++ b/docs/user-guide/common/beam-center-finder.ipynb @@ -408,7 +408,7 @@ "p.ax.axhline(0, color='cyan')\n", "p.ax.plot(0, 0, '+', color='k', ms=10)\n", "dx = 0.25\n", - "style = dict(ha='center', va='center', color='w')\n", + "style = dict(ha='center', va='center', color='w') # noqa: C408\n", "p.ax.text(dx, dx, 'North-East', **style)\n", "p.ax.text(-dx, dx, 'North-West', **style)\n", "p.ax.text(dx, -dx, 'South-East', **style)\n", @@ -433,7 +433,7 @@ "metadata": {}, "outputs": [], "source": [ - "kwargs = dict(\n", + "kwargs = dict( # noqa: C408\n", " data=masked,\n", " norm=workflow.compute(NormWavelengthTerm[SampleRun]),\n", " graph=workflow.compute(sans.conversions.ElasticCoordTransformGraph),\n", diff --git a/docs/user-guide/isis/sans2d.ipynb b/docs/user-guide/isis/sans2d.ipynb index 03a33bca..580a8f72 100644 --- a/docs/user-guide/isis/sans2d.ipynb +++ b/docs/user-guide/isis/sans2d.ipynb @@ -283,7 +283,7 @@ ")\n", "parts = (CleanSummedQ[SampleRun, Numerator], CleanSummedQ[SampleRun, Denominator])\n", "iofqs = (IofQ[SampleRun], IofQ[BackgroundRun], BackgroundSubtractedIofQ)\n", - "keys = monitors + (MaskedData[SampleRun],) + parts + iofqs\n", + "keys = (*monitors, MaskedData[SampleRun], *parts, *iofqs)\n", "\n", "results = workflow.compute(keys)\n", "\n", diff --git a/docs/user-guide/isis/zoom.ipynb b/docs/user-guide/isis/zoom.ipynb index 270ed393..03e347e1 100644 --- a/docs/user-guide/isis/zoom.ipynb +++ b/docs/user-guide/isis/zoom.ipynb @@ -213,7 +213,7 @@ ")\n", "parts = (CleanSummedQ[SampleRun, Numerator], CleanSummedQ[SampleRun, Denominator])\n", "iofqs = (IofQ[SampleRun],)\n", - "keys = monitors + (MaskedData[SampleRun],) + parts + iofqs\n", + "keys = (*monitors, MaskedData[SampleRun], *parts, *iofqs)\n", "\n", "results = workflow.compute(keys)\n", "\n", diff --git a/docs/user-guide/loki/loki-direct-beam.ipynb b/docs/user-guide/loki/loki-direct-beam.ipynb index 2be77a72..8c95902a 100644 --- a/docs/user-guide/loki/loki-direct-beam.ipynb +++ b/docs/user-guide/loki/loki-direct-beam.ipynb @@ -38,14 +38,10 @@ }, "outputs": [], "source": [ - "import numpy as np\n", "import scipp as sc\n", - "import sciline\n", - "import scippneutron as scn\n", "import plopp as pp\n", "from ess import sans\n", "from ess import loki\n", - "from ess import isissans as isis\n", "from ess.sans.types import *" ] }, diff --git a/pyproject.toml b/pyproject.toml index abb95dc5..7102f7a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,17 +67,43 @@ filterwarnings = [ 'ignore:\n Sentinel is not a public part of the traitlets API:DeprecationWarning', ] -[tool.bandit] -# Excluding tests because bandit doesn't like `assert`. -exclude_dirs = ["docs/conf.py", "tests"] +[tool.ruff] +line-length = 88 +extend-include = ["*.ipynb"] +extend-exclude = [ + ".*", "__pycache__", "build", "dist", "install", +] + +[tool.ruff.lint] +# See https://docs.astral.sh/ruff/rules/ +select = ["B", "C4", "DTZ", "E", "F", "G", "I", "PERF", "PGH", "PT", "PYI", "RUF", "S", "T20", "UP", "W"] +ignore = [ + # Conflict with ruff format, see + # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "COM812", "COM819", "D206", "D300", "E111", "E114", "E117", "ISC001", "ISC002", "Q000", "Q001", "Q002", "Q003", "W191", +] +fixable = ["I001", "B010"] +isort.known-first-party = ["esssans"] +pydocstyle.convention = "numpy" -[tool.black] -skip-string-normalization = true +[tool.ruff.lint.per-file-ignores] +# those files have an increased risk of relying on import order +"__init__.py" = ["I"] +"tests/*" = [ + "S101", # asserts are fine in tests + "B018", # 'useless expressions' are ok because some tests just check for exceptions +] +"*.ipynb" = [ + "E501", # longer lines are sometimes more readable + "F403", # *-imports used with domain types + "F405", # linter may fail to find names because of *-imports + "I", # we don't collect imports at the top + "S101", # asserts are used for demonstration and are safe in notebooks + "T201", # printing is ok for demonstration purposes +] -[tool.isort] -skip_gitignore = true -profile = "black" -known_first_party = ["esssans"] +[tool.ruff.format] +quote-style = "preserve" [tool.mypy] strict = true @@ -87,7 +113,6 @@ enable_error_code = [ "redundant-expr", "truthy-bool", ] -show_error_codes = true warn_unreachable = true [project.entry-points."beamlime.stateless"] diff --git a/requirements/make_base.py b/requirements/make_base.py index 1e1f48e3..68a17e84 100644 --- a/requirements/make_base.py +++ b/requirements/make_base.py @@ -1,7 +1,6 @@ import sys from argparse import ArgumentParser from pathlib import Path -from typing import List import tomli @@ -20,7 +19,7 @@ """ -def write_dependencies(dependency_name: str, dependencies: List[str]) -> None: +def write_dependencies(dependency_name: str, dependencies: list[str]) -> None: path = Path(f"{dependency_name}.in") if path.exists(): sections = path.read_text().split(CUSTOM_AUTO_SEPARATOR) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 1ba190c5..00000000 --- a/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -# See https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length -max-line-length = 88 -extend-ignore = E203 diff --git a/src/ess/isissans/data.py b/src/ess/isissans/data.py index ec89faa6..be4bc8b0 100644 --- a/src/ess/isissans/data.py +++ b/src/ess/isissans/data.py @@ -2,7 +2,6 @@ # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) import sciline import scipp as sc - from ess.sans.data import Registry from ess.sans.types import ( BackgroundRun, diff --git a/src/ess/isissans/general.py b/src/ess/isissans/general.py index 82127c27..d49bb7a0 100644 --- a/src/ess/isissans/general.py +++ b/src/ess/isissans/general.py @@ -3,6 +3,7 @@ """ Providers for the ISIS instruments. """ + import scipp as sc from ..sans.types import ( @@ -37,7 +38,7 @@ def default_parameters() -> dict: return { CorrectForGravity: False, - DimsToKeep: tuple(), + DimsToKeep: (), MonitorOffset[Incident]: MonitorOffset(sc.vector([0, 0, 0], unit='m')), MonitorOffset[Transmission]: MonitorOffset(sc.vector([0, 0, 0], unit='m')), DetectorBankOffset: DetectorBankOffset(sc.vector([0, 0, 0], unit='m')), @@ -72,7 +73,7 @@ def data_to_tof( def monitor_to_tof( - da: ConfiguredReducibleMonitor[RunType, MonitorType] + da: ConfiguredReducibleMonitor[RunType, MonitorType], ) -> TofMonitor[RunType, MonitorType]: """Dummy conversion of monitor data to time-of-flight data. The monitor data already has a time-of-flight coordinate.""" diff --git a/src/ess/isissans/io.py b/src/ess/isissans/io.py index 4a5f739a..e0698913 100644 --- a/src/ess/isissans/io.py +++ b/src/ess/isissans/io.py @@ -3,10 +3,10 @@ """ File loading functions for ISIS data, NOT using Mantid. """ + from typing import NewType import scipp as sc - from ess.sans.types import MaskedDetectorIDs, PixelMaskFilename CalibrationFilename = NewType('CalibrationFilename', str) @@ -33,7 +33,7 @@ def read_xml_detector_masking(filename: PixelMaskFilename) -> MaskedDetectorIDs: """ import xml.etree.ElementTree as ET # nosec - tree = ET.parse(filename) # nosec + tree = ET.parse(filename) # noqa: S314 root = tree.getroot() masked_detids = [] diff --git a/src/ess/isissans/mantidio.py b/src/ess/isissans/mantidio.py index e35d22bd..b1ed1d1d 100644 --- a/src/ess/isissans/mantidio.py +++ b/src/ess/isissans/mantidio.py @@ -3,14 +3,14 @@ """ File loading functions for ISIS data using Mantid. """ + from typing import NewType, NoReturn import sciline import scipp as sc import scippneutron as scn -from scipp.constants import g - from ess.sans.types import DirectBeam, DirectBeamFilename, Filename, RunType, SampleRun +from scipp.constants import g from .data import LoadedFileContents from .io import CalibrationFilename diff --git a/src/ess/isissans/sans2d.py b/src/ess/isissans/sans2d.py index 1e788b98..e0598b03 100644 --- a/src/ess/isissans/sans2d.py +++ b/src/ess/isissans/sans2d.py @@ -4,7 +4,6 @@ import sciline import scipp as sc - from ess.sans import providers as sans_providers from ess.sans.types import MaskedData, SampleRun, ScatteringRunType, TofData diff --git a/src/ess/isissans/visualization.py b/src/ess/isissans/visualization.py index 8640f3f7..af3d5da3 100644 --- a/src/ess/isissans/visualization.py +++ b/src/ess/isissans/visualization.py @@ -3,6 +3,7 @@ """ Plotting functions for ISIS data. """ + import warnings from typing import Any @@ -44,7 +45,7 @@ def plot_flat_detector_xy( 'Cannot plot 2-D instrument view of data array with non-constant ' 'y coordinate along tubes. Use scippneutron.instrument_view instead.' ) - plot_kwargs = dict(aspect='equal') + plot_kwargs = {'aspect': 'equal'} plot_kwargs.update(kwargs) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) diff --git a/src/ess/isissans/zoom.py b/src/ess/isissans/zoom.py index 2bb91e28..6d355d62 100644 --- a/src/ess/isissans/zoom.py +++ b/src/ess/isissans/zoom.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) import sciline - from ess.sans import providers as sans_providers from .data import load_tutorial_direct_beam, load_tutorial_run diff --git a/src/ess/loki/general.py b/src/ess/loki/general.py index 48bb8d3b..a87d83e9 100644 --- a/src/ess/loki/general.py +++ b/src/ess/loki/general.py @@ -3,12 +3,10 @@ """ Default parameters, providers and utility functions for the loki workflow. """ -from typing import Optional import sciline import scipp as sc from ess.reduce import nexus - from ess.sans import providers as sans_providers from ..sans.common import gravity_vector @@ -48,7 +46,7 @@ def default_parameters() -> dict: return { CorrectForGravity: False, - DimsToKeep: tuple(), + DimsToKeep: (), NeXusMonitorName[Incident]: 'monitor_1', NeXusMonitorName[Transmission]: 'monitor_2', TransformationPath: 'transform', @@ -88,7 +86,7 @@ def LokiAtLarmorWorkflow() -> sciline.Pipeline: DETECTOR_BANK_RESHAPING = { 'larmor_detector': lambda x: x.fold( - dim='detector_number', sizes=dict(layer=4, tube=32, straw=7, pixel=512) + dim='detector_number', sizes={'layer': 4, 'tube': 32, 'straw': 7, 'pixel': 512} ) } @@ -126,7 +124,7 @@ def get_monitor_data( def _add_variances_and_coordinates( da: sc.DataArray, source_position: sc.Variable, - sample_position: Optional[sc.Variable] = None, + sample_position: sc.Variable | None = None, ) -> sc.DataArray: out = da.copy(deep=False) if out.bins is not None: diff --git a/src/ess/loki/io.py b/src/ess/loki/io.py index 57c3bcbd..70c47bd2 100644 --- a/src/ess/loki/io.py +++ b/src/ess/loki/io.py @@ -3,9 +3,9 @@ """ Loading and merging of LoKI data. """ + import scipp as sc from ess.reduce import nexus - from ess.sans.types import ( Filename, LoadedNeXusDetector, diff --git a/src/ess/loki/workflow.py b/src/ess/loki/workflow.py index 992e677d..bb5c97d3 100644 --- a/src/ess/loki/workflow.py +++ b/src/ess/loki/workflow.py @@ -3,8 +3,6 @@ import sciline import scipp as sc import scippnexus as snx -from scippneutron.io.nexus.load_nexus import JSONGroup - from ess.loki.general import ( get_monitor_data, get_source_position, @@ -24,12 +22,12 @@ WavelengthBins, WavelengthMonitor, ) +from scippneutron.io.nexus.load_nexus import JSONGroup class MonitorHistogram( sciline.ScopeTwoParams[RunType, MonitorType, sc.DataArray], sc.DataArray -): - ... +): ... def _hist_monitor_wavelength( diff --git a/src/ess/sans/__init__.py b/src/ess/sans/__init__.py index c38cc2b4..928672d6 100644 --- a/src/ess/sans/__init__.py +++ b/src/ess/sans/__init__.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) +# ruff: noqa: E402, F401 import importlib.metadata diff --git a/src/ess/sans/beam_center_finder.py b/src/ess/sans/beam_center_finder.py index 748de25b..0d67ce0a 100644 --- a/src/ess/sans/beam_center_finder.py +++ b/src/ess/sans/beam_center_finder.py @@ -2,7 +2,7 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) import uuid -from typing import Dict, List, NewType, Union +from typing import NewType import numpy as np import sciline @@ -117,7 +117,7 @@ def beam_center_from_center_of_mass( return _offsets_to_vector(data=summed, xy=xy, graph=graph) -def _offsets_to_vector(data: sc.DataArray, xy: List[float], graph: dict) -> sc.Variable: +def _offsets_to_vector(data: sc.DataArray, xy: list[float], graph: dict) -> sc.Variable: """ Convert x,y offsets inside the plane normal to the beam to a vector in absolute coordinates. @@ -133,15 +133,15 @@ def _offsets_to_vector(data: sc.DataArray, xy: List[float], graph: dict) -> sc.V def _iofq_in_quadrants( - xy: List[float], + xy: list[float], data: sc.DataArray, norm: sc.DataArray, graph: dict, - q_bins: Union[int, sc.Variable], + q_bins: int | sc.Variable, wavelength_bins: sc.Variable, transform: sc.Variable, pixel_shape: sc.DataGroup, -) -> Dict[str, sc.DataArray]: +) -> dict[str, sc.DataArray]: """ Compute the intensity as a function of Q inside 4 quadrants in Phi. @@ -191,7 +191,7 @@ def _iofq_in_quadrants( params[LabFrameTransform[SampleRun]] = transform params[ElasticCoordTransformGraph] = graph params[BeamCenter] = _offsets_to_vector(data=data, xy=xy, graph=graph) - params[DimsToKeep] = tuple() + params[DimsToKeep] = () params[WavelengthMask] = None params[WavelengthBands] = None @@ -223,7 +223,7 @@ def _iofq_in_quadrants( return out -def _cost(xy: List[float], *args) -> float: +def _cost(xy: list[float], *args) -> float: """ Cost function for determining how close the :math:`I(Q)` curves are in all four quadrants. The cost is defined as @@ -287,7 +287,7 @@ def _cost(xy: List[float], *args) -> float: 'try restricting your Q range, or increasing the size of your Q bins to ' 'improve statistics in the denominator.' ) - logger.info(f'Beam center finder: x={xy[0]}, y={xy[1]}, cost={out}') + logger.info('Beam center finder: x=%s, y=%s, cost=%s', xy[0], xy[1], out) return out @@ -408,17 +408,17 @@ def beam_center_from_iofq( the results for finding the beam center. This is what is now implemented in this version of the algorithm. - """ # noqa: E501 + """ from scipy.optimize import minimize logger = get_logger('sans') - logger.info(f'Requested minimizer: {minimizer}') - logger.info(f'Requested tolerance: {tolerance}') + logger.info('Requested minimizer: %s', minimizer) + logger.info('Requested tolerance: %s', tolerance) minimizer = minimizer or 'Nelder-Mead' tolerance = tolerance or 0.1 - logger.info(f'Using minimizer: {minimizer}') - logger.info(f'Using tolerance: {tolerance}') + logger.info('Using minimizer: %s', minimizer) + logger.info('Using tolerance: %s', tolerance) # Flatten positions dim which is required during the iterations for slicing with a # boolean mask @@ -431,7 +431,7 @@ def beam_center_from_iofq( # Use center of mass to get initial guess for beam center com_shift = beam_center_from_center_of_mass(data, graph) - logger.info(f'Initial guess for beam center: {com_shift}') + logger.info('Initial guess for beam center: %s', com_shift) coords = data.transform_coords( ['cylindrical_x', 'cylindrical_y'], graph=graph @@ -452,6 +452,6 @@ def beam_center_from_iofq( ) center = _offsets_to_vector(data=data, xy=res.x, graph=graph) - logger.info(f'Final beam center value: {center}') - logger.info(f'Beam center finder minimizer info: {res}') + logger.info('Final beam center value: %s', center) + logger.info('Beam center finder minimizer info: %s', res) return center diff --git a/src/ess/sans/common.py b/src/ess/sans/common.py index 54b51fbf..debfe23e 100644 --- a/src/ess/sans/common.py +++ b/src/ess/sans/common.py @@ -1,8 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) - import uuid -from typing import Optional import numpy as np import scipp as sc @@ -18,7 +16,7 @@ def gravity_vector() -> sc.Variable: def mask_range( - da: sc.DataArray, mask: sc.DataArray, name: Optional[str] = None + da: sc.DataArray, mask: sc.DataArray, name: str | None = None ) -> sc.DataArray: """ Mask a range on a data array. diff --git a/src/ess/sans/data.py b/src/ess/sans/data.py index 05db2211..ed80296d 100644 --- a/src/ess/sans/data.py +++ b/src/ess/sans/data.py @@ -1,10 +1,9 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from typing import Dict class Registry: - def __init__(self, instrument: str, files: Dict[str, str], version: str): + def __init__(self, instrument: str, files: dict[str, str], version: str): import pooch self._registry = pooch.create( diff --git a/src/ess/sans/direct_beam.py b/src/ess/sans/direct_beam.py index 60e4b90d..3fd2fe8c 100644 --- a/src/ess/sans/direct_beam.py +++ b/src/ess/sans/direct_beam.py @@ -1,8 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) - -from typing import List - import numpy as np import scipp as sc from sciline import Pipeline @@ -55,7 +52,7 @@ def _compute_efficiency_correction( return out.rename_dims({wavelength_band_dim: 'wavelength'}) -def direct_beam(*, workflow: Pipeline, I0: sc.Variable, niter: int = 5) -> List[dict]: +def direct_beam(*, workflow: Pipeline, I0: sc.Variable, niter: int = 5) -> list[dict]: """ Compute the direct beam function. diff --git a/src/ess/sans/i_of_q.py b/src/ess/sans/i_of_q.py index 6fb5b963..1bc70306 100644 --- a/src/ess/sans/i_of_q.py +++ b/src/ess/sans/i_of_q.py @@ -237,7 +237,7 @@ def _bin_in_q( # Make dims to flatten contiguous, keep wavelength as the last dim data_dims = list(stripped.dims) - for dim in to_flatten + ['wavelength']: + for dim in [*to_flatten, 'wavelength']: data_dims.remove(dim) data_dims.append(dim) stripped = stripped.transpose(data_dims) diff --git a/src/ess/sans/logging.py b/src/ess/sans/logging.py index 53196876..7dad85fe 100644 --- a/src/ess/sans/logging.py +++ b/src/ess/sans/logging.py @@ -19,16 +19,17 @@ import inspect import logging import logging.config +from collections.abc import Callable, Sequence from copy import copy from os import PathLike -from typing import Any, Callable, List, Optional, Sequence, Union +from typing import Any import scipp as sc import scippneutron as scn from scipp.utils import running_in_jupyter -def get_logger(subname: Optional[str] = None) -> logging.Logger: +def get_logger(subname: str | None = None) -> logging.Logger: """Return one of ess's loggers. Parameters @@ -49,7 +50,7 @@ def get_logger(subname: Optional[str] = None) -> logging.Logger: def log_call( - *, instrument: str, message: str = None, level: Union[int, str] = logging.INFO + *, instrument: str, message: str | None = None, level: int | str = logging.INFO ): """ Decorator that logs a message every time the function is called. @@ -109,7 +110,7 @@ def format(self, record: logging.LogRecord) -> str: return super().format(record) -def default_loggers_to_configure() -> List[logging.Logger]: +def default_loggers_to_configure() -> list[logging.Logger]: """ Return a list of all loggers that get configured by ess by default. """ @@ -124,13 +125,13 @@ def default_loggers_to_configure() -> List[logging.Logger]: def configure( *, - filename: Optional[Union[str, PathLike]] = 'scipp.ess.log', - file_level: Union[str, int] = logging.INFO, - stream_level: Union[str, int] = logging.WARNING, - widget_level: Union[str, int] = logging.INFO, + filename: str | PathLike | None = 'scipp.ess.log', + file_level: str | int = logging.INFO, + stream_level: str | int = logging.WARNING, + widget_level: str | int = logging.INFO, show_thread: bool = False, show_process: bool = False, - loggers: Optional[Sequence[Union[str, logging.Logger]]] = None, + loggers: Sequence[str | logging.Logger] | None = None, ): """Set up logging for the ess package. @@ -200,7 +201,7 @@ def configure( def configure_workflow( - workflow_name: Optional[str] = None, *, display: Optional[bool] = None, **kwargs + workflow_name: str | None = None, *, display: bool | None = None, **kwargs ) -> logging.Logger: """Configure logging for a reduction workflow. @@ -272,7 +273,7 @@ def greet(): ] -def _deduce_instrument_name(f: Any) -> Optional[str]: +def _deduce_instrument_name(f: Any) -> str | None: # Assumes package name: ess.[.subpackage] package = inspect.getmodule(f).__package__ components = package.split('.', 2) @@ -293,7 +294,7 @@ def _function_name(f: Callable) -> str: def _make_stream_handler( - level: Union[str, int], show_thread: bool, show_process: bool + level: str | int, show_thread: bool, show_process: bool ) -> logging.StreamHandler: handler = logging.StreamHandler() handler.setLevel(level) @@ -302,8 +303,8 @@ def _make_stream_handler( def _make_file_handler( - filename: Union[str, PathLike], - level: Union[str, int], + filename: str | PathLike, + level: str | int, show_thread: bool, show_process: bool, ) -> logging.FileHandler: @@ -314,13 +315,13 @@ def _make_file_handler( def _make_handlers( - filename: Optional[Union[str, PathLike]], - file_level: Union[str, int], - stream_level: Union[str, int], - widget_level: Union[str, int], + filename: str | PathLike | None, + file_level: str | int, + stream_level: str | int, + widget_level: str | int, show_thread: bool, show_process: bool, -) -> List[logging.Handler]: +) -> list[logging.Handler]: handlers = [_make_stream_handler(stream_level, show_thread, show_process)] if filename is not None: handlers.append( @@ -332,7 +333,7 @@ def _make_handlers( def _configure_logger( - logger: logging.Logger, handlers: List[logging.Handler], level: Union[str, int] + logger: logging.Logger, handlers: list[logging.Handler], level: str | int ): for handler in handlers: logger.addHandler(handler) @@ -348,16 +349,14 @@ def _configure_mantid_logging(level: str): pass -def _base_level(levels: List[Union[str, int]]) -> int: +def _base_level(levels: list[str | int]) -> int: return min( - ( - logging.getLevelName(level) if isinstance(level, str) else level - for level in levels - ) + logging.getLevelName(level) if isinstance(level, str) else level + for level in levels ) -def _mantid_version() -> Optional[str]: +def _mantid_version() -> str | None: try: import mantid diff --git a/src/ess/sans/masking.py b/src/ess/sans/masking.py index 9345d0dd..c343b2e3 100644 --- a/src/ess/sans/masking.py +++ b/src/ess/sans/masking.py @@ -3,6 +3,7 @@ """ Masking functions for the loki workflow. """ + import numpy as np import scipp as sc diff --git a/src/ess/sans/types.py b/src/ess/sans/types.py index e5babc59..d9e1acac 100644 --- a/src/ess/sans/types.py +++ b/src/ess/sans/types.py @@ -5,6 +5,7 @@ The domain types are used to define parameters and to request results from a Sciline pipeline.""" + from collections.abc import Sequence from enum import Enum from typing import NewType, TypeVar diff --git a/src/ess/sans/uncertainty.py b/src/ess/sans/uncertainty.py index a691e19a..9899a221 100644 --- a/src/ess/sans/uncertainty.py +++ b/src/ess/sans/uncertainty.py @@ -2,30 +2,30 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) """Tools for handling statistical uncertainties.""" -from typing import Dict, TypeVar, Union, overload +from typing import TypeVar, overload import scipp as sc -T = TypeVar("T", bound=Union[sc.Variable, sc.DataArray]) +T = TypeVar("T", bound=sc.Variable | sc.DataArray) @overload def broadcast_with_upper_bound_variances( - data: sc.Variable, sizes: Dict[str, int] + data: sc.Variable, sizes: dict[str, int] ) -> sc.Variable: pass @overload def broadcast_with_upper_bound_variances( - data: sc.DataArray, sizes: Dict[str, int] + data: sc.DataArray, sizes: dict[str, int] ) -> sc.DataArray: pass def broadcast_with_upper_bound_variances( - data: Union[sc.Variable, sc.DataArray], sizes: Dict[str, int] -) -> Union[sc.Variable, sc.DataArray]: + data: sc.Variable | sc.DataArray, sizes: dict[str, int] +) -> sc.Variable | sc.DataArray: if _no_variance_broadcast(data, sizes): return data size = 1 @@ -38,15 +38,15 @@ def broadcast_with_upper_bound_variances( def drop_variances_if_broadcast( - data: Union[sc.Variable, sc.DataArray], sizes: Dict[str, int] -) -> Union[sc.Variable, sc.DataArray]: + data: sc.Variable | sc.DataArray, sizes: dict[str, int] +) -> sc.Variable | sc.DataArray: if _no_variance_broadcast(data, sizes): return data return sc.values(data) def _no_variance_broadcast( - data: Union[sc.Variable, sc.DataArray], sizes: Dict[str, int] + data: sc.Variable | sc.DataArray, sizes: dict[str, int] ) -> bool: return (data.variances is None) or all( data.sizes.get(dim) == size for dim, size in sizes.items() diff --git a/src/ess/sans/workflow.py b/src/ess/sans/workflow.py index 3c8a7f48..d04ae90e 100644 --- a/src/ess/sans/workflow.py +++ b/src/ess/sans/workflow.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from typing import Hashable, Iterable +from collections.abc import Hashable, Iterable import pandas as pd import sciline diff --git a/tests/common_test.py b/tests/common_test.py index 234d4e77..21f78560 100644 --- a/tests/common_test.py +++ b/tests/common_test.py @@ -4,7 +4,6 @@ import numpy as np import pytest import scipp as sc - from ess.sans.common import mask_range diff --git a/tests/i_of_q_test.py b/tests/i_of_q_test.py index 1a2074df..258d8a36 100644 --- a/tests/i_of_q_test.py +++ b/tests/i_of_q_test.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) import scipp as sc - from ess import sans merge_func = sans.workflow.merge_contributions diff --git a/tests/io_test.py b/tests/io_test.py index 57019e75..a853b52d 100644 --- a/tests/io_test.py +++ b/tests/io_test.py @@ -6,13 +6,12 @@ import scipp as sc import scipp.testing import scippnexus as snx -from scippnexus.application_definitions import nxcansas - from ess.sans.io import save_background_subtracted_iofq from ess.sans.types import BackgroundSubtractedIofQ, OutFilename, RunNumber, RunTitle +from scippnexus.application_definitions import nxcansas -@pytest.mark.parametrize('use_edges', (True, False)) +@pytest.mark.parametrize('use_edges', [True, False]) def test_save_background_subtracted_iofq(use_edges, tmp_path): def background_subtracted_iofq() -> BackgroundSubtractedIofQ: i = sc.arange('Q', 0.0, 400.0) diff --git a/tests/isissans/sans2d_reduction_test.py b/tests/isissans/sans2d_reduction_test.py index b4108a71..1a80226d 100644 --- a/tests/isissans/sans2d_reduction_test.py +++ b/tests/isissans/sans2d_reduction_test.py @@ -1,11 +1,10 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from typing import Callable, List +from collections.abc import Callable import pytest import sciline import scipp as sc - from ess import isissans as isis from ess import sans from ess.isissans import MonitorOffset, SampleOffset, sans2d @@ -71,7 +70,7 @@ def make_params() -> dict: params[CorrectForGravity] = True params[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.upper_bound params[ReturnEvents] = False - params[DimsToKeep] = tuple() + params[DimsToKeep] = () return params @@ -145,9 +144,9 @@ def test_workflow_is_deterministic(): def test_pipeline_raises_VariancesError_if_normalization_errors_not_dropped(): params = make_params() - params[ - NonBackgroundWavelengthRange - ] = None # Make sure we raise in iofq_denominator + params[NonBackgroundWavelengthRange] = ( + None # Make sure we raise in iofq_denominator + ) params[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.fail pipeline = sciline.Pipeline(sans2d_providers(), params=params) with pytest.raises(sc.VariancesError): @@ -181,10 +180,12 @@ def test_pipeline_can_compute_intermediate_results(): # TODO See scipp/sciline#57 for plans on a builtin way to do this -def as_dict(funcs: List[Callable[..., type]]) -> dict: +def as_dict(funcs: list[Callable[..., type]]) -> dict: from typing import get_type_hints - return dict(zip([get_type_hints(func)['return'] for func in funcs], funcs)) + return dict( + zip([get_type_hints(func)['return'] for func in funcs], funcs, strict=True) + ) def pixel_dependent_direct_beam( diff --git a/tests/isissans/zoom_reduction_test.py b/tests/isissans/zoom_reduction_test.py index 391b44ba..8bc6b980 100644 --- a/tests/isissans/zoom_reduction_test.py +++ b/tests/isissans/zoom_reduction_test.py @@ -2,7 +2,6 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) import sciline import scipp as sc - from ess import isissans as isis from ess import sans from ess.sans.types import ( diff --git a/tests/loki/common.py b/tests/loki/common.py index d9a27c02..a066eba5 100644 --- a/tests/loki/common.py +++ b/tests/loki/common.py @@ -1,9 +1,8 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) -from typing import Callable, List +from collections.abc import Callable import scipp as sc - from ess import loki, sans from ess.sans.types import ( BackgroundRun, @@ -30,12 +29,12 @@ def make_params(no_masks: bool = True) -> dict: params[NeXusDetectorName] = 'larmor_detector' params[Filename[SampleRun]] = loki.data.loki_tutorial_sample_run_60339() params[Filename[BackgroundRun]] = loki.data.loki_tutorial_background_run_60393() - params[ - Filename[TransmissionRun[SampleRun]] - ] = loki.data.loki_tutorial_sample_transmission_run() - params[ - Filename[TransmissionRun[BackgroundRun]] - ] = loki.data.loki_tutorial_run_60392() + params[Filename[TransmissionRun[SampleRun]]] = ( + loki.data.loki_tutorial_sample_transmission_run() + ) + params[Filename[TransmissionRun[BackgroundRun]]] = ( + loki.data.loki_tutorial_run_60392() + ) params[Filename[EmptyBeamRun]] = loki.data.loki_tutorial_run_60392() params[WavelengthBins] = sc.linspace( @@ -56,7 +55,7 @@ def make_params(no_masks: bool = True) -> dict: return params -def loki_providers_no_beam_center_finder() -> List[Callable]: +def loki_providers_no_beam_center_finder() -> list[Callable]: from ess.isissans.io import read_xml_detector_masking return list( @@ -69,7 +68,8 @@ def loki_providers_no_beam_center_finder() -> List[Callable]: ) -def loki_providers() -> List[Callable]: - return loki_providers_no_beam_center_finder() + [ - sans.beam_center_finder.beam_center_from_center_of_mass +def loki_providers() -> list[Callable]: + return [ + *loki_providers_no_beam_center_finder(), + sans.beam_center_finder.beam_center_from_center_of_mass, ] diff --git a/tests/loki/directbeam_test.py b/tests/loki/directbeam_test.py index adef944e..e4b3295f 100644 --- a/tests/loki/directbeam_test.py +++ b/tests/loki/directbeam_test.py @@ -5,13 +5,12 @@ import sciline import scipp as sc -from scipp.scipy.interpolate import interp1d - from ess import loki, sans from ess.sans.types import DimsToKeep, QBins, WavelengthBands, WavelengthBins +from scipp.scipy.interpolate import interp1d sys.path.insert(0, str(Path(__file__).resolve().parent)) -from common import loki_providers, make_params # noqa: E402 +from common import loki_providers, make_params def _get_I0(qbins: sc.Variable) -> sc.Variable: diff --git a/tests/loki/iofq_test.py b/tests/loki/iofq_test.py index 377ad92e..22346ca3 100644 --- a/tests/loki/iofq_test.py +++ b/tests/loki/iofq_test.py @@ -7,8 +7,6 @@ import pytest import sciline import scipp as sc -from scipp.testing import assert_identical - from ess import loki, sans from ess.sans.conversions import ElasticCoordTransformGraph from ess.sans.types import ( @@ -34,9 +32,10 @@ WavelengthBands, WavelengthBins, ) +from scipp.testing import assert_identical sys.path.insert(0, str(Path(__file__).resolve().parent)) -from common import ( # noqa: E402 +from common import ( loki_providers, loki_providers_no_beam_center_finder, make_params, diff --git a/tests/loki/live_reduction_test.py b/tests/loki/live_reduction_test.py index 76aec700..65c3c1e1 100644 --- a/tests/loki/live_reduction_test.py +++ b/tests/loki/live_reduction_test.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) import sciline as sl - from ess.loki.workflow import LoKiMonitorWorkflow diff --git a/tests/normalization_test.py b/tests/normalization_test.py index 5d62578e..ec5b18d0 100644 --- a/tests/normalization_test.py +++ b/tests/normalization_test.py @@ -4,7 +4,6 @@ import numpy as np import pytest import scipp as sc - from ess.isissans.data import sans2d_solid_angle_reference from ess.sans import normalization @@ -29,7 +28,7 @@ def _sans2d_geometry(): } # Rotate +y to -x transform = sc.spatial.rotation(value=[0, 0, 1 / 2**0.5, 1 / 2**0.5]) - return dict(pixel_shape=pixel_shape, transform=transform) + return {'pixel_shape': pixel_shape, 'transform': transform} def _mantid_sans2d_solid_angle_data(): diff --git a/tests/package_test.py b/tests/package_test.py index 218b68c8..9c5ac938 100644 --- a/tests/package_test.py +++ b/tests/package_test.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2024 Scipp contributors (https://github.com/scipp) import pytest - from ess import isissans, loki, sans diff --git a/tests/uncertainty_test.py b/tests/uncertainty_test.py index e7fd0247..f317097b 100644 --- a/tests/uncertainty_test.py +++ b/tests/uncertainty_test.py @@ -2,9 +2,8 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) import scipp as sc -from scipp.testing import assert_identical - from ess.sans.uncertainty import broadcast_with_upper_bound_variances +from scipp.testing import assert_identical def test_broadcast_returns_original_if_no_new_dims(): diff --git a/tox.ini b/tox.ini index c096cde9..1f1091fb 100644 --- a/tox.ini +++ b/tox.ini @@ -10,14 +10,14 @@ commands = pytest {posargs} [testenv:nightly] deps = -r requirements/nightly.txt -commands = pytest +commands = pytest {posargs} [testenv:unpinned] description = Test with unpinned dependencies, as a user would install now. deps = -r requirements/basetest.txt esssans -commands = pytest +commands = pytest {posargs} [testenv:docs] description = invoke sphinx-build to build the HTML docs