diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f5b304f..4f7d2320 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,10 +11,10 @@ jobs: name: Build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.14" cache: "pip" - name: Install dependencies run: | @@ -24,7 +24,7 @@ jobs: run: python -m build --sdist - name: Check the package run: python -m twine check dist/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: cibw-sdist path: dist/*.tar.gz @@ -34,19 +34,21 @@ jobs: name: Test source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.14" cache: "pip" - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: name: cibw-sdist path: dist + - name: Install debian packages needed + run: sudo apt-get install libxkbcommon0 libegl1 - name: Install from sdist run: | pip install "$(ls dist/fabio-*.tar.gz)" - pip install pyqt5 matplotlib + pip install qtpy pyside6 matplotlib - name: Run tests run: python -c "import fabio.test, sys; sys.exit(fabio.test.run_tests())" @@ -54,10 +56,10 @@ jobs: name: Build documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.14" cache: "pip" - name: Install pandoc&graphviz run: sudo apt-get install pandoc graphviz @@ -72,7 +74,7 @@ jobs: export FABIO_VERSION="$(python -c 'import fabio; print(fabio.strictversion)')" sphinx-build doc/source/ "fabio-${FABIO_VERSION}_documentation/" zip -r "fabio-${FABIO_VERSION}_documentation.zip" "fabio-${FABIO_VERSION}_documentation/" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: documentation path: fabio-*_documentation.zip @@ -93,40 +95,38 @@ jobs: cibw_archs: "ppc64le" - os: windows-2022 cibw_archs: "auto64" - - os: macos-13 - cibw_archs: "x86_64" - macos_target: "11.0" - - os: macos-14 + # - os: macos-13 + # cibw_archs: "x86_64" + # macos_target: "11.0" + - os: macos-latest cibw_archs: "arm64" macos_target: "11.0" steps: - - uses: actions/checkout@v4 - - uses: docker/setup-qemu-action@v3 + - uses: actions/checkout@v5 + - uses: docker/setup-qemu-action@v4 if: runner.os == 'Linux' with: platforms: all - - uses: pypa/cibuildwheel@v3.2.1 + - uses: pypa/cibuildwheel@v3.3.1 env: # Use silx wheelhouse: needed for ppc64le CIBW_ENVIRONMENT_LINUX: "PIP_FIND_LINKS=https://www.silx.org/pub/wheelhouse/ PIP_TRUSTED_HOST=www.silx.org" CIBW_BEFORE_BUILD: "pip install --prefer-binary h5py" - CIBW_BEFORE_TEST: "pip install --prefer-binary h5py" + CIBW_BEFORE_TEST: "pip install --prefer-binary h5py qtpy pyside6 matplotlib" CIBW_BUILD_VERBOSITY: 1 CIBW_BUILD: cp311-* cp312-* cp313-* cp314-* # Do not build for pypy and muslinux CIBW_SKIP: pp* *-musllinux_* CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" # Install test dependencies + CIBW_BEFORE_TEST_LINUX: yum install -y mesa-libEGL mesa-libGL libxkbcommon-x11 libxkbcommon xcb-util-image xcb-util-keysyms libXrandr xcb-util-renderutil libXcursor libxcb CIBW_TEST_COMMAND: python -c "import fabio.test, sys; sys.exit(fabio.test.run_tests())" # Skip tests for emulated architectures - # and Python3.8 on macos/arm64 (https://github.com/pypa/cibuildwheel/pull/1169) - CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x} cp38-macosx_*" - - - uses: actions/upload-artifact@v4 + CIBW_TEST_SKIP: "*-*linux_{aarch64,ppc64le,s390x}" + - uses: actions/upload-artifact@v7 with: name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} path: ./wheelhouse/*.whl @@ -143,7 +143,7 @@ jobs: # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: pattern: cibw-* path: dist diff --git a/doc/source/Changelog.rst b/doc/source/Changelog.rst index ac9174ca..9c26d950 100644 --- a/doc/source/Changelog.rst +++ b/doc/source/Changelog.rst @@ -1,10 +1,23 @@ Changelog ========= -FabIO-2026.xx.y: -................ -- Migrate viewer application to `qtpy` -- Supports Python 3.11+ through Python's limited API +FabIO-2026.6.0: +............... +- Migrate viewer application to `qtpy` for Qt-version independence (PySide6/PyQt5/PyQt6) +- Introduce ``ENDIANNESS`` enum and ``FabioImage.get_stype()`` helper to build dtypes with + explicit byte order; migrate all codec and image-format modules away from + ``ndarray.byteswap()`` to endian-typed reads/writes (#310) +- Support reading images from byte-streams and ``BytesIO`` objects (#612) +- Privatise ``_do_magic()`` in ``openimage`` for consistency +- All remaining GPL-licensed source files converted to MIT license +- Helper function to raise the open-file ``ulimit`` on Linux systems (#568) +- Fix resource-download locking strategy using ``filelock`` +- Fix compatibility with NumPy ≥ 2.5 (deprecation of in-place shape assignment) +- Fix deprecation warnings introduced in Python 3.14 +- Update ``lxml`` requirement to ≥ 6.1.1 +- Code clean-up: ``bruker100image`` and ``kcdimage`` format handlers simplified +- Ruff linting now enforced in CI +- Drop Python 3.10; supports Python 3.11 - 3.14+ (Python limited API) FabIO-2025.10.0: ................ diff --git a/doc/source/coverage.rst b/doc/source/coverage.rst index edce1343..91a56508 100644 --- a/doc/source/coverage.rst +++ b/doc/source/coverage.rst @@ -1,61 +1,61 @@ Test coverage report for fabio ============================== -Measured on *fabio* version 2026.3.0a0, 16/06/2026 +Measured on *fabio* version 2026.6.0, 17/06/2026 .. csv-table:: Test suite coverage :header: "Name", "Stmts", "Exec", "Cover" :widths: 35, 8, 8, 8 - "GEimage.py", "115", "89", "77.4 %" + "GEimage.py", "113", "88", "77.9 %" "HiPiCimage.py", "60", "53", "88.3 %" - "OXDimage.py", "351", "324", "92.3 %" - "TiffIO.py", "781", "643", "82.3 %" + "OXDimage.py", "341", "323", "94.7 %" + "TiffIO.py", "778", "641", "82.4 %" "__init__.py", "33", "27", "81.8 %" "adscimage.py", "3", "3", "100.0 %" "binaryimage.py", "61", "43", "70.5 %" - "bruker100image.py", "285", "242", "84.9 %" - "brukerimage.py", "203", "169", "83.3 %" + "bruker100image.py", "283", "241", "85.2 %" + "brukerimage.py", "201", "169", "84.1 %" "cbfimage.py", "643", "422", "65.6 %" "converters.py", "16", "13", "81.2 %" "datIO.py", "30", "7", "23.3 %" "directories.py", "22", "17", "77.3 %" - "dm3image.py", "152", "143", "94.1 %" - "dtrekimage.py", "169", "140", "82.8 %" - "edfimage.py", "920", "726", "78.9 %" + "dm3image.py", "156", "146", "93.6 %" + "dtrekimage.py", "155", "130", "83.9 %" + "edfimage.py", "927", "726", "78.3 %" "eigerimage.py", "195", "139", "71.3 %" "esperantoimage.py", "158", "137", "86.7 %" "fabioformats.py", "88", "65", "73.9 %" - "fabioimage.py", "469", "387", "82.5 %" - "fabioutils.py", "391", "311", "79.5 %" + "fabioimage.py", "474", "392", "82.7 %" + "fabioutils.py", "409", "328", "80.2 %" "file_series.py", "369", "279", "75.6 %" - "fit2dimage.py", "90", "75", "83.3 %" - "fit2dmaskimage.py", "78", "73", "93.6 %" + "fit2dimage.py", "88", "73", "83.0 %" + "fit2dmaskimage.py", "74", "72", "97.3 %" "fit2dspreadsheetimage.py", "46", "39", "84.8 %" "hdf5image.py", "97", "66", "68.0 %" "jpeg2kimage.py", "83", "55", "66.3 %" "jpegimage.py", "46", "44", "95.7 %" - "kcdimage.py", "106", "75", "70.8 %" + "kcdimage.py", "103", "73", "70.9 %" "lambdaimage.py", "146", "115", "78.8 %" "limaimage.py", "179", "150", "83.8 %" - "mar345image.py", "270", "250", "92.6 %" + "mar345image.py", "265", "243", "91.7 %" "marccdimage.py", "65", "57", "87.7 %" "mpaimage.py", "55", "50", "90.9 %" "mrcimage.py", "84", "64", "76.2 %" "nexus.py", "231", "128", "55.4 %" "numpyimage.py", "76", "52", "68.4 %" - "openimage.py", "132", "113", "85.6 %" + "openimage.py", "135", "116", "85.9 %" "pilatusimage.py", "43", "38", "88.4 %" "pixiimage.py", "106", "90", "84.9 %" - "pnmimage.py", "136", "85", "62.5 %" - "raxisimage.py", "102", "90", "88.2 %" + "pnmimage.py", "131", "83", "63.4 %" + "raxisimage.py", "94", "83", "88.3 %" "sparseimage.py", "155", "105", "67.7 %" "speimage.py", "161", "156", "96.9 %" "templateimage.py", "24", "16", "66.7 %" "tifimage.py", "127", "120", "94.5 %" - "version.py", "39", "38", "97.4 %" + "version.py", "39", "34", "87.2 %" "xcaliburimage.py", "565", "441", "78.1 %" - "xsdimage.py", "93", "69", "74.2 %" + "xsdimage.py", "91", "68", "74.7 %" "app/__init__.py", "0", "0", "0.0 %" "app/convert.py", "203", "27", "13.3 %" "app/densify.py", "178", "36", "20.2 %" @@ -63,19 +63,13 @@ Measured on *fabio* version 2026.3.0a0, 16/06/2026 "app/eiger2crysalis.py", "370", "41", "11.1 %" "app/hdf2neggia.py", "203", "32", "15.8 %" "app/viewer.py", "27", "15", "55.6 %" - "app/viewer_qtpy.py", "840", "62", "7.4 %" "benchmark/__init__.py", "30", "15", "50.0 %" "compression/__init__.py", "1", "1", "100.0 %" - "compression/agi_bitfield.py", "171", "148", "86.5 %" - "compression/compression.py", "244", "187", "76.6 %" + "compression/agi_bitfield.py", "170", "148", "87.1 %" + "compression/compression.py", "231", "183", "79.2 %" "ext/__init__.py", "0", "0", "0.0 %" "qt/__init__.py", "0", "0", "0.0 %" - "qt/_pyqt6.py", "30", "28", "93.3 %" - "qt/_pyside_dynamic.py", "75", "5", "6.7 %" - "qt/_qt.py", "144", "58", "40.3 %" - "qt/_utils.py", "21", "9", "42.9 %" "qt/dialogs.py", "126", "15", "11.9 %" - "qt/inspect.py", "25", "12", "48.0 %" "qt/matplotlib.py", "70", "25", "35.7 %" "qt/viewer.py", "823", "58", "7.0 %" "test/__init__.py", "19", "12", "63.2 %" @@ -83,8 +77,8 @@ Measured on *fabio* version 2026.3.0a0, 16/06/2026 "test/codecs/__init__.py", "86", "80", "93.0 %" "utils/ExternalResources.py", "266", "176", "66.2 %" "utils/__init__.py", "0", "0", "0.0 %" - "utils/cli.py", "59", "47", "79.7 %" + "utils/cli.py", "75", "58", "77.3 %" "utils/deprecation.py", "66", "62", "93.9 %" - "utils/pilutils.py", "48", "38", "79.2 %" + "utils/pilutils.py", "51", "39", "76.5 %" - "fabio total", "13318", "8281", "62.2 %" + "fabio total", "12163", "8102", "66.6 %" diff --git a/src/fabio/utils/testutils.py b/src/fabio/utils/testutils.py index 50222e0a..8511d799 100644 --- a/src/fabio/utils/testutils.py +++ b/src/fabio/utils/testutils.py @@ -121,18 +121,13 @@ class LoggingCounter(logging.Handler): :type logger: str or :class:`logging.Logger` """ - def __init__( - self, - logger=None, - ): + def __init__(self, logger=None): if logger is None: logger = logging.getLogger() elif not isinstance(logger, logging.Logger): logger = logging.getLogger(logger) self.logger = logger - self.records = [] - super(LoggingCounter, self).__init__() def __enter__(self): @@ -198,7 +193,6 @@ class TestLogging(LoggingCounter): Default: Do not check. :raises RuntimeError: If the message counts are the expected ones. """ - def __init__( self, logger=None, @@ -209,14 +203,7 @@ def __init__( debug=None, notset=None, ): - if logger is None: - logger = logging.getLogger() - elif not isinstance(logger, logging.Logger): - logger = logging.getLogger(logger) - self.logger = logger - - self.records = [] - + super(TestLogging, self).__init__(logger) self.count_by_level = { logging.CRITICAL: critical, logging.ERROR: error, @@ -226,8 +213,6 @@ def __init__( logging.NOTSET: notset, } - super(TestLogging, self).__init__() - def __exit__(self, exc_type, exc_value, traceback): """Context (i.e., with) support"""