From 72b6bfdfadd4cf9e01ae04da041bd8ac121a0382 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Mon, 17 Jul 2023 20:00:35 -0400 Subject: [PATCH 1/6] CI: Add 3.12 pre-release tests --- .github/workflows/pre-release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 630f09d99..4431c7135 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] architecture: ['x64', 'x86'] install: ['pip'] check: ['test'] @@ -54,6 +54,8 @@ jobs: architecture: x86 - os: macos-latest architecture: x86 + - python-version: '3.12' + architecture: x86 env: DEPENDS: ${{ matrix.depends }} @@ -72,6 +74,7 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} + allow-prereleases: true - name: Display Python version run: python -c "import sys; print(sys.version)" - name: Create virtual environment From 59b93afc3cf82e6830e292687932dd3f2110d66c Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 15 Aug 2023 09:01:06 -0400 Subject: [PATCH 2/6] TEST: Mark file:/// URL test as xfail --- nibabel/tests/test_image_api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index 091bc57e8..890619bad 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -25,6 +25,7 @@ import io import pathlib +import sys import warnings from functools import partial from itertools import product @@ -579,6 +580,10 @@ def validate_from_url(self, imaker, params): del img del rt_img + @pytest.mark.xfail( + sys.version_info >= (3, 12), + reason='Response type for file: urls is not a stream in Python 3.12', + ) def validate_from_file_url(self, imaker, params): tmp_path = self.tmp_path From 06c1e76be6c5f945d0812961e22f808e2334404e Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 15 Aug 2023 09:10:27 -0400 Subject: [PATCH 3/6] CI: Disable building dependencies from source --- tools/ci/install_dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci/install_dependencies.sh b/tools/ci/install_dependencies.sh index f26c5204c..2ea4a524e 100755 --- a/tools/ci/install_dependencies.sh +++ b/tools/ci/install_dependencies.sh @@ -19,10 +19,10 @@ if [ -n "$EXTRA_PIP_FLAGS" ]; then fi if [ -n "$DEPENDS" ]; then - pip install ${EXTRA_PIP_FLAGS} --prefer-binary ${!DEPENDS} + pip install ${EXTRA_PIP_FLAGS} --only-binary :all: ${!DEPENDS} if [ -n "$OPTIONAL_DEPENDS" ]; then for DEP in ${!OPTIONAL_DEPENDS}; do - pip install ${EXTRA_PIP_FLAGS} --prefer-binary $DEP || true + pip install ${EXTRA_PIP_FLAGS} --only-binary :all: $DEP || true done fi fi From eb39c08156bdc1ccf1ae197128eec7226696da8e Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 23 Aug 2023 10:27:01 -0400 Subject: [PATCH 4/6] FIX: Hack around 3.12rc1 bug (python/cpython#108111) --- nibabel/openers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nibabel/openers.py b/nibabel/openers.py index 90c7774d1..9a024680a 100644 --- a/nibabel/openers.py +++ b/nibabel/openers.py @@ -78,6 +78,12 @@ def __init__( mtime=mtime, ) + def seek(self, pos: int, whence: int = 0, /) -> int: + # Work around bug (gh-180111) in Python 3.12rc1, where seeking without + # flushing can cause write of excess null bytes + self.flush() + return super().seek(pos, whence) + def _gzip_open( filename: str, From 2eba8dbf000155cf6666aef76ee07c7a61a2951c Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Fri, 8 Sep 2023 16:44:04 -0400 Subject: [PATCH 5/6] TEST: Use tmp_path and explicit delete to appease Windows tempdir cleanup --- nibabel/tests/test_openers.py | 59 ++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/nibabel/tests/test_openers.py b/nibabel/tests/test_openers.py index 0d150a145..a228e6613 100644 --- a/nibabel/tests/test_openers.py +++ b/nibabel/tests/test_openers.py @@ -127,35 +127,36 @@ def patch_indexed_gzip(state): yield -def test_Opener_gzip_type(): - # Test that BufferedGzipFile or IndexedGzipFile are used as appropriate - - data = 'this is some test data' - fname = 'test.gz' - - with InTemporaryDirectory(): - - # make some test data - with GzipFile(fname, mode='wb') as f: - f.write(data.encode()) - - # Each test is specified by a tuple containing: - # (indexed_gzip present, Opener kwargs, expected file type) - tests = [ - (False, {'mode': 'rb', 'keep_open': True}, GzipFile), - (False, {'mode': 'rb', 'keep_open': False}, GzipFile), - (False, {'mode': 'wb', 'keep_open': True}, GzipFile), - (False, {'mode': 'wb', 'keep_open': False}, GzipFile), - (True, {'mode': 'rb', 'keep_open': True}, MockIndexedGzipFile), - (True, {'mode': 'rb', 'keep_open': False}, MockIndexedGzipFile), - (True, {'mode': 'wb', 'keep_open': True}, GzipFile), - (True, {'mode': 'wb', 'keep_open': False}, GzipFile), - ] - - for test in tests: - igzip_present, kwargs, expected = test - with patch_indexed_gzip(igzip_present): - assert isinstance(Opener(fname, **kwargs).fobj, expected) +def test_Opener_gzip_type(tmp_path): + # Test that GzipFile or IndexedGzipFile are used as appropriate + + data = b'this is some test data' + fname = tmp_path / 'test.gz' + + # make some test data + with GzipFile(fname, mode='wb') as f: + f.write(data) + + # Each test is specified by a tuple containing: + # (indexed_gzip present, Opener kwargs, expected file type) + tests = [ + (False, {'mode': 'rb', 'keep_open': True}, GzipFile), + (False, {'mode': 'rb', 'keep_open': False}, GzipFile), + (False, {'mode': 'wb', 'keep_open': True}, GzipFile), + (False, {'mode': 'wb', 'keep_open': False}, GzipFile), + (True, {'mode': 'rb', 'keep_open': True}, MockIndexedGzipFile), + (True, {'mode': 'rb', 'keep_open': False}, MockIndexedGzipFile), + (True, {'mode': 'wb', 'keep_open': True}, GzipFile), + (True, {'mode': 'wb', 'keep_open': False}, GzipFile), + ] + + for test in tests: + igzip_present, kwargs, expected = test + with patch_indexed_gzip(igzip_present): + opener = Opener(fname, **kwargs) + assert isinstance(opener.fobj, expected) + # Explicit close to appease Windows + del opener class TestImageOpener(unittest.TestCase): From a42321f44fef53fe6e13fdaa9eaa9804fd5a05ef Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Fri, 8 Sep 2023 17:11:18 -0400 Subject: [PATCH 6/6] TEST: Use a less finicky method of creating temporary files --- nibabel/streamlines/tests/test_streamlines.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nibabel/streamlines/tests/test_streamlines.py b/nibabel/streamlines/tests/test_streamlines.py index dfb74042a..300397b2b 100644 --- a/nibabel/streamlines/tests/test_streamlines.py +++ b/nibabel/streamlines/tests/test_streamlines.py @@ -84,7 +84,7 @@ def setup(): ) -def test_is_supported_detect_format(): +def test_is_supported_detect_format(tmp_path): # Test is_supported and detect_format functions # Empty file/string f = BytesIO() @@ -103,7 +103,8 @@ def test_is_supported_detect_format(): # Wrong extension but right magic number for tfile_cls in FORMATS.values(): - with tempfile.TemporaryFile(mode='w+b', suffix='.txt') as f: + fpath = tmp_path / 'test.txt' + with open(fpath, 'w+b') as f: f.write(asbytes(tfile_cls.MAGIC_NUMBER)) f.seek(0, os.SEEK_SET) assert nib.streamlines.is_supported(f) @@ -111,7 +112,8 @@ def test_is_supported_detect_format(): # Good extension but wrong magic number for ext, tfile_cls in FORMATS.items(): - with tempfile.TemporaryFile(mode='w+b', suffix=ext) as f: + fpath = tmp_path / f'test{ext}' + with open(fpath, 'w+b') as f: f.write(b'pass') f.seek(0, os.SEEK_SET) assert not nib.streamlines.is_supported(f)