Skip to content

Commit db4c859

Browse files
authored
Merge pull request #1413 from effigies/mnt/updates
chore: Resolve CI failures
2 parents 84294f4 + 6948625 commit db4c859

13 files changed

+75
-58
lines changed

nibabel/_typing.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Helpers for typing compatibility across Python versions"""
2+
3+
import sys
4+
5+
if sys.version_info < (3, 10):
6+
from typing_extensions import ParamSpec
7+
else:
8+
from typing import ParamSpec
9+
10+
if sys.version_info < (3, 11):
11+
from typing_extensions import Self
12+
else:
13+
from typing import Self
14+
15+
if sys.version_info < (3, 13):
16+
from typing_extensions import TypeVar
17+
else:
18+
from typing import TypeVar
19+
20+
21+
__all__ = [
22+
'ParamSpec',
23+
'Self',
24+
'TypeVar',
25+
]

nibabel/arrayproxy.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@
5959

6060
if ty.TYPE_CHECKING:
6161
import numpy.typing as npt
62-
from typing_extensions import Self # PY310
62+
63+
from ._typing import Self, TypeVar
6364

6465
# Taken from numpy/__init__.pyi
65-
_DType = ty.TypeVar('_DType', bound=np.dtype[ty.Any])
66+
_DType = TypeVar('_DType', bound=np.dtype[ty.Any])
6667

6768

6869
class ArrayLike(ty.Protocol):

nibabel/dataobj_images.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@
2020
if ty.TYPE_CHECKING:
2121
import numpy.typing as npt
2222

23+
from ._typing import Self
2324
from .arrayproxy import ArrayLike
2425
from .fileholders import FileMap
2526
from .filename_parser import FileSpec
2627

27-
ArrayImgT = ty.TypeVar('ArrayImgT', bound='DataobjImage')
28-
2928

3029
class DataobjImage(FileBasedImage):
3130
"""Template class for images that have dataobj data stores"""
@@ -427,12 +426,12 @@ def ndim(self) -> int:
427426

428427
@classmethod
429428
def from_file_map(
430-
klass: type[ArrayImgT],
429+
klass,
431430
file_map: FileMap,
432431
*,
433432
mmap: bool | ty.Literal['c', 'r'] = True,
434433
keep_file_open: bool | None = None,
435-
) -> ArrayImgT:
434+
) -> Self:
436435
"""Class method to create image from mapping in ``file_map``
437436
438437
Parameters
@@ -466,12 +465,12 @@ def from_file_map(
466465

467466
@classmethod
468467
def from_filename(
469-
klass: type[ArrayImgT],
468+
klass,
470469
filename: FileSpec,
471470
*,
472471
mmap: bool | ty.Literal['c', 'r'] = True,
473472
keep_file_open: bool | None = None,
474-
) -> ArrayImgT:
473+
) -> Self:
475474
"""Class method to create image from filename `filename`
476475
477476
Parameters

nibabel/deprecated.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55
import typing as ty
66
import warnings
77

8+
from ._typing import ParamSpec
89
from .deprecator import Deprecator
910
from .pkg_info import cmp_pkg_version
1011

11-
if ty.TYPE_CHECKING:
12-
# PY39: ParamSpec is available in Python 3.10+
13-
P = ty.ParamSpec('P')
14-
else:
15-
# Just to keep the runtime happy
16-
P = ty.TypeVar('P')
12+
P = ParamSpec('P')
1713

1814

1915
class ModuleProxy:

nibabel/filebasedimages.py

+10-16
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,11 @@
2121
from .openers import ImageOpener
2222

2323
if ty.TYPE_CHECKING:
24+
from ._typing import Self
2425
from .filename_parser import ExtensionSpec, FileSpec
2526

2627
FileSniff = tuple[bytes, str]
2728

28-
ImgT = ty.TypeVar('ImgT', bound='FileBasedImage')
29-
HdrT = ty.TypeVar('HdrT', bound='FileBasedHeader')
30-
31-
StreamImgT = ty.TypeVar('StreamImgT', bound='SerializableImage')
32-
3329

3430
class ImageFileError(Exception):
3531
pass
@@ -39,7 +35,7 @@ class FileBasedHeader:
3935
"""Template class to implement header protocol"""
4036

4137
@classmethod
42-
def from_header(klass: type[HdrT], header: FileBasedHeader | ty.Mapping | None = None) -> HdrT:
38+
def from_header(klass, header: FileBasedHeader | ty.Mapping | None = None) -> Self:
4339
if header is None:
4440
return klass()
4541
# I can't do isinstance here because it is not necessarily true
@@ -53,7 +49,7 @@ def from_header(klass: type[HdrT], header: FileBasedHeader | ty.Mapping | None =
5349
)
5450

5551
@classmethod
56-
def from_fileobj(klass: type[HdrT], fileobj: io.IOBase) -> HdrT:
52+
def from_fileobj(klass, fileobj: io.IOBase) -> Self:
5753
raise NotImplementedError
5854

5955
def write_to(self, fileobj: io.IOBase) -> None:
@@ -65,7 +61,7 @@ def __eq__(self, other: object) -> bool:
6561
def __ne__(self, other: object) -> bool:
6662
return not self == other
6763

68-
def copy(self: HdrT) -> HdrT:
64+
def copy(self) -> Self:
6965
"""Copy object to independent representation
7066
7167
The copy should not be affected by any changes to the original
@@ -245,12 +241,12 @@ def set_filename(self, filename: str) -> None:
245241
self.file_map = self.__class__.filespec_to_file_map(filename)
246242

247243
@classmethod
248-
def from_filename(klass: type[ImgT], filename: FileSpec) -> ImgT:
244+
def from_filename(klass, filename: FileSpec) -> Self:
249245
file_map = klass.filespec_to_file_map(filename)
250246
return klass.from_file_map(file_map)
251247

252248
@classmethod
253-
def from_file_map(klass: type[ImgT], file_map: FileMap) -> ImgT:
249+
def from_file_map(klass, file_map: FileMap) -> Self:
254250
raise NotImplementedError
255251

256252
@classmethod
@@ -360,7 +356,7 @@ def instance_to_filename(klass, img: FileBasedImage, filename: FileSpec) -> None
360356
img.to_filename(filename)
361357

362358
@classmethod
363-
def from_image(klass: type[ImgT], img: FileBasedImage) -> ImgT:
359+
def from_image(klass, img: FileBasedImage) -> Self:
364360
"""Class method to create new instance of own class from `img`
365361
366362
Parameters
@@ -540,7 +536,7 @@ def _filemap_from_iobase(klass, io_obj: io.IOBase) -> FileMap:
540536
return klass.make_file_map({klass.files_types[0][0]: io_obj})
541537

542538
@classmethod
543-
def from_stream(klass: type[StreamImgT], io_obj: io.IOBase) -> StreamImgT:
539+
def from_stream(klass, io_obj: io.IOBase) -> Self:
544540
"""Load image from readable IO stream
545541
546542
Convert to BytesIO to enable seeking, if input stream is not seekable
@@ -567,7 +563,7 @@ def to_stream(self, io_obj: io.IOBase, **kwargs) -> None:
567563
self.to_file_map(self._filemap_from_iobase(io_obj), **kwargs)
568564

569565
@classmethod
570-
def from_bytes(klass: type[StreamImgT], bytestring: bytes) -> StreamImgT:
566+
def from_bytes(klass, bytestring: bytes) -> Self:
571567
"""Construct image from a byte string
572568
573569
Class method
@@ -598,9 +594,7 @@ def to_bytes(self, **kwargs) -> bytes:
598594
return bio.getvalue()
599595

600596
@classmethod
601-
def from_url(
602-
klass: type[StreamImgT], url: str | request.Request, timeout: float = 5
603-
) -> StreamImgT:
597+
def from_url(klass, url: str | request.Request, timeout: float = 5) -> Self:
604598
"""Retrieve and load an image from a URL
605599
606600
Class method

nibabel/gifti/gifti.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ def to_xml(self, enc='utf-8', *, mode='strict', **kwargs) -> bytes:
867867
if arr.datatype not in GIFTI_DTYPES:
868868
arr = copy(arr)
869869
# TODO: Better typing for recoders
870-
dtype = cast(np.dtype, data_type_codes.dtype[arr.datatype])
870+
dtype = cast('np.dtype', data_type_codes.dtype[arr.datatype])
871871
if np.issubdtype(dtype, np.floating):
872872
arr.datatype = data_type_codes['float32']
873873
elif np.issubdtype(dtype, np.integer):

nibabel/loadsave.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from __future__ import annotations
1313

1414
import os
15-
import typing as ty
1615

1716
import numpy as np
1817

@@ -26,13 +25,17 @@
2625
_compressed_suffixes = ('.gz', '.bz2', '.zst')
2726

2827

29-
if ty.TYPE_CHECKING:
28+
TYPE_CHECKING = False
29+
if TYPE_CHECKING:
30+
from typing import TypedDict
31+
32+
from ._typing import ParamSpec
3033
from .filebasedimages import FileBasedImage
3134
from .filename_parser import FileSpec
3235

33-
P = ty.ParamSpec('P')
36+
P = ParamSpec('P')
3437

35-
class Signature(ty.TypedDict):
38+
class Signature(TypedDict):
3639
signature: bytes
3740
format_name: str
3841

nibabel/nifti1.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,15 @@
1414
from __future__ import annotations
1515

1616
import json
17-
import sys
1817
import typing as ty
1918
import warnings
2019
from io import BytesIO
2120

2221
import numpy as np
2322
import numpy.linalg as npl
2423

25-
if sys.version_info < (3, 13):
26-
from typing_extensions import Self, TypeVar # PY312
27-
else:
28-
from typing import Self, TypeVar
29-
3024
from . import analyze # module import
25+
from ._typing import Self, TypeVar
3126
from .arrayproxy import get_obj_dtype
3227
from .batteryrunners import Report
3328
from .casting import have_binary128

nibabel/openers.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
from types import TracebackType
2323

2424
from _typeshed import WriteableBuffer
25-
from typing_extensions import Self
25+
26+
from ._typing import Self
2627

2728
ModeRT = ty.Literal['r', 'rt']
2829
ModeRB = ty.Literal['rb']
@@ -68,7 +69,7 @@ def __init__(
6869
if filename is None:
6970
raise TypeError('Must define either fileobj or filename')
7071
# Cast because GzipFile.myfileobj has type io.FileIO while open returns ty.IO
71-
fileobj = self.myfileobj = ty.cast(io.FileIO, open(filename, modestr))
72+
fileobj = self.myfileobj = ty.cast('io.FileIO', open(filename, modestr))
7273
super().__init__(
7374
filename='',
7475
mode=modestr,

nibabel/pointset.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
from nibabel.spatialimages import SpatialImage
3232

3333
if ty.TYPE_CHECKING:
34-
from typing_extensions import Self
34+
from ._typing import Self, TypeVar
3535

36-
_DType = ty.TypeVar('_DType', bound=np.dtype[ty.Any])
36+
_DType = TypeVar('_DType', bound=np.dtype[ty.Any])
3737

3838

3939
class CoordinateArray(ty.Protocol):

nibabel/spatialimages.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138

139139
import numpy as np
140140

141+
from ._typing import TypeVar
141142
from .casting import sctypes_aliases
142143
from .dataobj_images import DataobjImage
143144
from .filebasedimages import FileBasedHeader, FileBasedImage
@@ -152,11 +153,11 @@
152153

153154
import numpy.typing as npt
154155

156+
from ._typing import Self
155157
from .arrayproxy import ArrayLike
156158
from .fileholders import FileMap
157159

158-
SpatialImgT = ty.TypeVar('SpatialImgT', bound='SpatialImage')
159-
SpatialHdrT = ty.TypeVar('SpatialHdrT', bound='SpatialHeader')
160+
SpatialImgT = TypeVar('SpatialImgT', bound='SpatialImage')
160161

161162

162163
class HasDtype(ty.Protocol):
@@ -203,9 +204,9 @@ def __init__(
203204

204205
@classmethod
205206
def from_header(
206-
klass: type[SpatialHdrT],
207+
klass,
207208
header: SpatialProtocol | FileBasedHeader | ty.Mapping | None = None,
208-
) -> SpatialHdrT:
209+
) -> Self:
209210
if header is None:
210211
return klass()
211212
# I can't do isinstance here because it is not necessarily true
@@ -227,7 +228,7 @@ def __eq__(self, other: object) -> bool:
227228
)
228229
return NotImplemented
229230

230-
def copy(self: SpatialHdrT) -> SpatialHdrT:
231+
def copy(self) -> Self:
231232
"""Copy object to independent representation
232233
233234
The copy should not be affected by any changes to the original
@@ -586,7 +587,7 @@ def set_data_dtype(self, dtype: npt.DTypeLike) -> None:
586587
self._header.set_data_dtype(dtype)
587588

588589
@classmethod
589-
def from_image(klass: type[SpatialImgT], img: SpatialImage | FileBasedImage) -> SpatialImgT:
590+
def from_image(klass, img: SpatialImage | FileBasedImage) -> Self:
590591
"""Class method to create new instance of own class from `img`
591592
592593
Parameters
@@ -610,7 +611,7 @@ def from_image(klass: type[SpatialImgT], img: SpatialImage | FileBasedImage) ->
610611
return super().from_image(img)
611612

612613
@property
613-
def slicer(self: SpatialImgT) -> SpatialFirstSlicer[SpatialImgT]:
614+
def slicer(self) -> SpatialFirstSlicer[Self]:
614615
"""Slicer object that returns cropped and subsampled images
615616
616617
The image is resliced in the current orientation; no rotation or
@@ -658,7 +659,7 @@ def orthoview(self) -> OrthoSlicer3D:
658659
"""
659660
return OrthoSlicer3D(self.dataobj, self.affine, title=self.get_filename())
660661

661-
def as_reoriented(self: SpatialImgT, ornt: Sequence[Sequence[int]]) -> SpatialImgT:
662+
def as_reoriented(self, ornt: Sequence[Sequence[int]]) -> Self:
662663
"""Apply an orientation change and return a new image
663664
664665
If ornt is identity transform, return the original image, unchanged

nibabel/volumeutils.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828

2929
import numpy.typing as npt
3030

31+
from ._typing import TypeVar
32+
3133
Scalar = np.number | float
3234

33-
K = ty.TypeVar('K')
34-
V = ty.TypeVar('V')
35-
DT = ty.TypeVar('DT', bound=np.generic)
35+
K = TypeVar('K')
36+
V = TypeVar('V')
37+
DT = TypeVar('DT', bound=np.generic)
3638

3739
sys_is_le = sys.byteorder == 'little'
3840
native_code: ty.Literal['<', '>'] = '<' if sys_is_le else '>'
@@ -969,7 +971,7 @@ def working_type(
969971

970972

971973
def int_scinter_ftype(
972-
ifmt: type[np.integer],
974+
ifmt: np.dtype[np.integer] | type[np.integer],
973975
slope: npt.ArrayLike = 1.0,
974976
inter: npt.ArrayLike = 0.0,
975977
default: type[np.floating] = np.float32,

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ readme = "README.rst"
1111
license = { text = "MIT License" }
1212
requires-python = ">=3.9"
1313
dependencies = [
14-
"numpy >=1.22",
14+
"numpy >=1.23",
1515
"packaging >=20",
1616
"importlib_resources >=5.12; python_version < '3.12'",
1717
"typing_extensions >=4.6; python_version < '3.13'",

0 commit comments

Comments
 (0)