Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
version: "latest"
args: "check --fix-only --exit-non-zero-on-fix"
- uses: pre-commit-ci/lite-action@v1.1.0
if: always()
Expand All @@ -27,7 +28,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.10', '3.11', '3.12']
python-version: ['3.12']
fail-fast: false

env:
Expand Down
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
/dist
.mplconfig

# INSTALL.md recommends a venv that should not be committed
venv
.venv

# doc build
/docs/sphinx-docs/build

Expand All @@ -42,6 +46,3 @@ tests.log
/installers/dist
*.exe
sasdata/_version.py

# Local pre-commit hooks
.pre-commit-config.yaml
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
default_install_hook_types: [pre-commit, pre-push]

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.1
hooks:
# Run the linter, applying any available fixes
- id: ruff-check
stages: [ pre-commit, pre-push ]
args: [ --fix-only ]
11 changes: 11 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest


def pytest_addoption(parser):
parser.addoption(
"--show_plots", action="store_true", default=False, help="Display diagnostic plots during tests"
)

@pytest.fixture
def show_plots(request):
return request.config.getoption("--show_plots")
27 changes: 26 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dynamic = [
]
description = "Sas Data Loader application"
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.12"
license = { text = "BSD-3-Clause" }
authors = [
{name = "SasView Team", email = "developers@sasview.org"},
Expand Down Expand Up @@ -118,3 +118,28 @@ testpaths = [
norecursedirs = [
"sasdata",
]

[tool.ruff]
line-length = 120

[tool.ruff.lint]
# The ruff rules are available at: https://docs.astral.sh/ruff/rules/
select = ["E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"W", # pycodestyle warnings
"SIM118", # Use `key in dict` instead of `key in dict.keys()`
"SIM300"] # Yoda condition detected


ignore = ["E501", # line too long (leave to formatter)
"UP008", # Use `super()` instead of `super(__class__, self)`
"UP031"] # Use format specifiers instead of percent format

[tool.ruff.lint.isort.sections]
# Group all SasView and SasModels imports into a separate section.
"sas" = ["sas", "sasmodels"]

[tool.ruff.lint.isort]
section-order = ["future", "standard-library", "third-party", "sas", "first-party", "local-folder"]
13 changes: 13 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ lxml

# Calculation
numpy
scipy

# Unit testing
pytest
unittest-xml-reporting

# Documentation (future)
sphinx
html5lib

# Other stuff
matplotlib
pre-commit
19 changes: 11 additions & 8 deletions sasdata/data_util/formatnum.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
where the number in parenthesis is the uncertainty in the last two digits of v.

:func:`format_uncertainty` uses the compact format by default, but this
can be changed to use the expanded +/- format by setting
can be changed to use the expanded +/- format by setting
format_uncertainty.compact to False.

The formatted string uses only the number of digits warranted by
the uncertainty in the measurement.

Expand All @@ -37,7 +37,9 @@
formatter.compact flag.
"""
import math

import numpy as np

__all__ = ['format_uncertainty', 'format_uncertainty_pm',
'format_uncertainty_compact']

Expand Down Expand Up @@ -85,7 +87,7 @@ class UncertaintyFormatter:
True or False. The default is True.
"""
compact = True

def __call__(self, value, uncertainty):
"""
Given *value* and *uncertainty*, return a string representation.
Expand Down Expand Up @@ -140,7 +142,7 @@ def _format_uncertainty(value, uncertainty, compact):
# Extreme cases: zeros before value or after error
# The value is ###.###(##)e#, ##.####(##)e# or #.#####(##)e#
pass

# Force engineering notation, with exponent a multiple of 3
val_place = int(math.floor(val_place / 3.)) * 3

Expand Down Expand Up @@ -279,7 +281,7 @@ def test_compact():
assert value_str(-np.inf,None) == "-inf"
assert value_str(np.inf,None) == "inf"
assert value_str(np.nan,None) == "NaN"

# bad or missing uncertainty
assert value_str(-1.23567,np.nan) == "-1.23567"
assert value_str(-1.23567,-np.inf) == "-1.23567"
Expand Down Expand Up @@ -410,7 +412,7 @@ def test_pm():
assert value_str(-np.inf,None) == "-inf"
assert value_str(np.inf,None) == "inf"
assert value_str(np.nan,None) == "NaN"

# bad or missing uncertainty
assert value_str(-1.23567,np.nan) == "-1.23567"
assert value_str(-1.23567,-np.inf) == "-1.23567"
Expand All @@ -432,8 +434,9 @@ def main():
test_compact()
test_pm()
test_default()

import doctest
doctest.testmod()

if __name__ == "__main__": main()
if __name__ == "__main__":
main()
9 changes: 4 additions & 5 deletions sasdata/data_util/loader_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
Exceptions specific to loading data.
"""

from typing import Optional


class NoKnownLoaderException(Exception):
Expand All @@ -11,7 +10,7 @@ class NoKnownLoaderException(Exception):
extension of the loaded file. This exception should only be thrown by
loader.py.
"""
def __init__(self, e: Optional[str] = None):
def __init__(self, e: str | None = None):
self.message = e


Expand All @@ -20,7 +19,7 @@ class DefaultReaderException(Exception):
Exception for files with no associated reader. This should be thrown by
default readers only to tell Loader to try the next reader.
"""
def __init__(self, e: Optional[str] = None):
def __init__(self, e: str | None = None):
self.message = e


Expand All @@ -29,7 +28,7 @@ class FileContentsException(Exception):
Exception for files with an associated reader, but with no loadable data.
This is useful for catching loader or file format issues.
"""
def __init__(self, e: Optional[str] = None):
def __init__(self, e: str | None = None):
self.message = e


Expand All @@ -39,5 +38,5 @@ class DataReaderException(Exception):
along the way.
Any exceptions of this type should be put into the datainfo.errors
"""
def __init__(self, e: Optional[str] = None):
def __init__(self, e: str | None = None):
self.message = e
8 changes: 4 additions & 4 deletions sasdata/data_util/manipulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

# TODO: copy the meta data from the 2D object to the resulting 1D object
import math

import numpy as np
from typing import Optional, Union

from sasdata.dataloader.data_info import Data1D, Data2D

Expand All @@ -41,7 +41,7 @@ def position_and_wavelength_to_q(dx: float, dy: float, detector_distance: float,
return (4.0 * math.pi / wavelength) * math.sin(theta)


def get_q_compo(dx: float, dy: float, detector_distance: float, wavelength: float, compo: Optional[str] = None) -> float:
def get_q_compo(dx: float, dy: float, detector_distance: float, wavelength: float, compo: str | None = None) -> float:
"""
This reduces tiny error at very large q.
Implementation of this func is not started yet.<--ToDo
Expand Down Expand Up @@ -102,7 +102,7 @@ def get_pixel_fraction_square(x: float, x_min: float, x_max: float) -> float:
return 1.0


def get_intercept(q: float, q_0: float, q_1: float) -> Union[float, None]:
def get_intercept(q: float, q_0: float, q_1: float) -> float | None:
"""
Returns the fraction of the side at which the
q-value intercept the pixel, None otherwise.
Expand Down Expand Up @@ -235,7 +235,7 @@ def get_dq_data(data2d: Data2D) -> np.array:
################################################################################


def reader2D_converter(data2d: Optional[Data2D] = None) -> Data2D:
def reader2D_converter(data2d: Data2D | None = None) -> Data2D:
"""
convert old 2d format opened by IhorReader or danse_reader
to new Data2D format
Expand Down
31 changes: 16 additions & 15 deletions sasdata/data_util/nxsunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@

import math
import re
from typing import Dict, Union, TypeVar, Tuple, Sequence, Optional, List
from collections.abc import Sequence
from typing import TypeVar

__all__ = ['Converter', 'standardize_units']
T = TypeVar('T')
ConversionType = Union[float, Tuple[float, float]]
ConversionType = float | tuple[float, float]
DIMENSIONS = {} # type: Dict[str, Dict[str, ConversionType]]
AMBIGUITIES = {} # type: Dict[str, str]
PREFIX = dict(peta=1e15, tera=1e12, giga=1e9, mega=1e6, kilo=1e3, deci=1e-1, centi=1e-2, milli=1e-3, mili=1e-3,
Expand All @@ -60,7 +61,7 @@
# Limited form of units for returning objects of a specific type.
# Maybe want to do full units handling with e.g., pyre's
# unit class. For now lets keep it simple. Note that
def _build_metric_units(unit: str, abbr: str) -> Dict[str, float]:
def _build_metric_units(unit: str, abbr: str) -> dict[str, float]:
"""
Construct standard SI names for the given unit.
Builds e.g.,
Expand Down Expand Up @@ -89,7 +90,7 @@ def _build_metric_units(unit: str, abbr: str) -> Dict[str, float]:
return units


def _build_plural_units(**kw: Dict[str, ConversionType]) -> Dict[str, ConversionType]:
def _build_plural_units(**kw: dict[str, ConversionType]) -> dict[str, ConversionType]:
"""
Construct names for the given units. Builds singular and plural form.
"""
Expand All @@ -99,7 +100,7 @@ def _build_plural_units(**kw: Dict[str, ConversionType]) -> Dict[str, Conversion
return units


def _build_degree_units(name: str, symbol: str, conversion: ConversionType) -> Dict[str, ConversionType]:
def _build_degree_units(name: str, symbol: str, conversion: ConversionType) -> dict[str, ConversionType]:
"""
Builds variations on the temperature unit name, including the degree
symbol or the word degree.
Expand All @@ -118,7 +119,7 @@ def _build_degree_units(name: str, symbol: str, conversion: ConversionType) -> D


def _build_inv_n_units(names: Sequence[str], conversion: ConversionType,
n: int = 2) -> Dict[str, ConversionType]:
n: int = 2) -> dict[str, ConversionType]:
"""
Builds variations on inverse x to the nth power units, including 1/x^n, invx^n, x^-n and x^{-n}.
"""
Expand All @@ -132,7 +133,7 @@ def _build_inv_n_units(names: Sequence[str], conversion: ConversionType,
return units


def _build_inv_n_metric_units(unit: str, abbr: str, n: int = 2) -> Dict[str, ConversionType]:
def _build_inv_n_metric_units(unit: str, abbr: str, n: int = 2) -> dict[str, ConversionType]:
"""
Using the return from _build_metric_units, build inverse to the nth power variations on all units
(1/x^n, invx^n, x^{-n} and x^-n)
Expand Down Expand Up @@ -265,7 +266,7 @@ def _build_all_units():
DIMENSIONS['dimensionless'] = unknown


def standardize_units(unit: Union[str, None]) -> List[str]:
def standardize_units(unit: str | None) -> list[str]:
"""
Convert supplied units to a standard format for maintainability
:param unit: Raw unit as supplied
Expand Down Expand Up @@ -301,7 +302,7 @@ def standardize_units(unit: Union[str, None]) -> List[str]:
return _format_unit_structure(unit)


def _format_unit_structure(unit: Optional[str] = None) -> List[str]:
def _format_unit_structure(unit: str | None = None) -> list[str]:
"""
Format units a common way
:param unit: Unit string to be formatted
Expand Down Expand Up @@ -373,7 +374,7 @@ def units(self) -> str:
def units(self, unit: str):
self._units = standardize_units(unit)

def __init__(self, units: Optional[str] = None, dimension: Optional[List[str]] = None):
def __init__(self, units: str | None = None, dimension: list[str] | None = None):
self.units = units if units is not None else 'a.u.' # type: str

# Lookup dimension if not given
Expand All @@ -398,20 +399,20 @@ def __init__(self, units: Optional[str] = None, dimension: Optional[List[str]] =
self.scalebase = base[0]
self.scaleoffset = base[1]

def scale(self, units: str = "", value: T = None) -> Union[List[float], T]:
def scale(self, units: str = "", value: T = None) -> list[float] | T:
"""Scale the given value using the units string supplied"""
units = standardize_units(units) if units is not None else ['']
base = self._get_scale_for_units(units)
value = self._scale_with_offset(value, base)
return value

def _scale_with_offset(self, value: float, scale_base: Tuple[float, float]) -> float:
def _scale_with_offset(self, value: float, scale_base: tuple[float, float]) -> float:
"""Scale the given value and add the offset using the units string supplied"""
inscale, inoffset = self.scalebase, self.scaleoffset
outscale, outoffset = scale_base
return (value + outoffset) * inscale / outscale - inoffset

def _get_scale_for_units(self, units: List[str]):
def _get_scale_for_units(self, units: list[str]):
"""Protected method to get scale factor and scale offset as a combined value"""
base = (1.0, 0.0)
for scalemap, unit in zip(self.scalemap, units):
Expand All @@ -423,7 +424,7 @@ def _get_scale_for_units(self, units: List[str]):
base = (base[0] * unit_scale[0], base[1] + unit_scale[1])
return base

def get_compatible_units(self) -> List[str]:
def get_compatible_units(self) -> list[str]:
"""Return a list of compatible units for the current Convertor object"""
unique_units = []
conv_list = []
Expand All @@ -436,7 +437,7 @@ def get_compatible_units(self) -> List[str]:
unique_units = [x for _, x in sorted(zip(conv_list, unique_units))]
return unique_units

def __call__(self, value: T, units: Optional[str] = "") -> Union[List[float], T]:
def __call__(self, value: T, units: str | None = "") -> list[float] | T:
# Note: calculating a*1 rather than simply returning a would produce
# an unnecessary copy of the array, which in the case of the raw
# counts array would be bad. Sometimes copying and other times
Expand Down
Loading