Skip to content

Commit 2e1814c

Browse files
committed
TYP: Use type variables to annotate filebasedimage classes
1 parent b748783 commit 2e1814c

File tree

1 file changed

+40
-32
lines changed

1 file changed

+40
-32
lines changed

nibabel/filebasedimages.py

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
FileMap = ty.Mapping[str, FileHolder]
2525
FileSniff = ty.Tuple[bytes, str]
2626

27+
ImgT = ty.TypeVar('ImgT', bound='FileBasedImage')
28+
HdrT = ty.TypeVar('HdrT', bound='FileBasedHeader')
29+
30+
StreamImgT = ty.TypeVar('StreamImgT', bound='SerializableImage')
31+
2732

2833
class ImageFileError(Exception):
2934
pass
@@ -33,7 +38,7 @@ class FileBasedHeader:
3338
"""Template class to implement header protocol"""
3439

3540
@classmethod
36-
def from_header(klass, header=None):
41+
def from_header(klass: type[HdrT], header: FileBasedHeader | ty.Mapping | None = None) -> HdrT:
3742
if header is None:
3843
return klass()
3944
# I can't do isinstance here because it is not necessarily true
@@ -47,19 +52,19 @@ def from_header(klass, header=None):
4752
)
4853

4954
@classmethod
50-
def from_fileobj(klass, fileobj: io.IOBase):
51-
raise NotImplementedError
55+
def from_fileobj(klass: type[HdrT], fileobj: io.IOBase) -> HdrT:
56+
raise NotImplementedError # pragma: no cover
5257

53-
def write_to(self, fileobj: io.IOBase):
54-
raise NotImplementedError
58+
def write_to(self, fileobj: io.IOBase) -> None:
59+
raise NotImplementedError # pragma: no cover
5560

56-
def __eq__(self, other):
57-
raise NotImplementedError
61+
def __eq__(self, other: object) -> bool:
62+
raise NotImplementedError # pragma: no cover
5863

59-
def __ne__(self, other):
64+
def __ne__(self, other: object) -> bool:
6065
return not self == other
6166

62-
def copy(self) -> FileBasedHeader:
67+
def copy(self: HdrT) -> HdrT:
6368
"""Copy object to independent representation
6469
6570
The copy should not be affected by any changes to the original
@@ -153,6 +158,7 @@ class FileBasedImage:
153158
"""
154159

155160
header_class: Type[FileBasedHeader] = FileBasedHeader
161+
_header: FileBasedHeader
156162
_meta_sniff_len: int = 0
157163
files_types: tuple[tuple[str, str | None], ...] = (('image', None),)
158164
valid_exts: tuple[str, ...] = ()
@@ -186,7 +192,7 @@ def __init__(
186192
self._header = self.header_class.from_header(header)
187193
if extra is None:
188194
extra = {}
189-
self.extra = extra
195+
self.extra = dict(extra)
190196

191197
if file_map is None:
192198
file_map = self.__class__.make_file_map()
@@ -196,7 +202,7 @@ def __init__(
196202
def header(self) -> FileBasedHeader:
197203
return self._header
198204

199-
def __getitem__(self, key):
205+
def __getitem__(self, key) -> None:
200206
"""No slicing or dictionary interface for images"""
201207
raise TypeError('Cannot slice image objects.')
202208

@@ -221,7 +227,7 @@ def get_filename(self) -> str | None:
221227
characteristic_type = self.files_types[0][0]
222228
return self.file_map[characteristic_type].filename
223229

224-
def set_filename(self, filename: str):
230+
def set_filename(self, filename: str) -> None:
225231
"""Sets the files in the object from a given filename
226232
227233
The different image formats may check whether the filename has
@@ -239,16 +245,16 @@ def set_filename(self, filename: str):
239245
self.file_map = self.__class__.filespec_to_file_map(filename)
240246

241247
@classmethod
242-
def from_filename(klass, filename: FileSpec):
248+
def from_filename(klass: type[ImgT], filename: FileSpec) -> ImgT:
243249
file_map = klass.filespec_to_file_map(filename)
244250
return klass.from_file_map(file_map)
245251

246252
@classmethod
247-
def from_file_map(klass, file_map: FileMap):
248-
raise NotImplementedError
253+
def from_file_map(klass: type[ImgT], file_map: FileMap) -> ImgT:
254+
raise NotImplementedError # pragma: no cover
249255

250256
@classmethod
251-
def filespec_to_file_map(klass, filespec: FileSpec):
257+
def filespec_to_file_map(klass, filespec: FileSpec) -> FileMap:
252258
"""Make `file_map` for this class from filename `filespec`
253259
254260
Class method
@@ -282,7 +288,7 @@ def filespec_to_file_map(klass, filespec: FileSpec):
282288
file_map[key] = FileHolder(filename=fname)
283289
return file_map
284290

285-
def to_filename(self, filename: FileSpec, **kwargs):
291+
def to_filename(self, filename: FileSpec, **kwargs) -> None:
286292
r"""Write image to files implied by filename string
287293
288294
Parameters
@@ -301,11 +307,11 @@ def to_filename(self, filename: FileSpec, **kwargs):
301307
self.file_map = self.filespec_to_file_map(filename)
302308
self.to_file_map(**kwargs)
303309

304-
def to_file_map(self, file_map: FileMap | None = None, **kwargs):
305-
raise NotImplementedError
310+
def to_file_map(self, file_map: FileMap | None = None, **kwargs) -> None:
311+
raise NotImplementedError # pragma: no cover
306312

307313
@classmethod
308-
def make_file_map(klass, mapping: ty.Mapping[str, str | io.IOBase] | None = None):
314+
def make_file_map(klass, mapping: ty.Mapping[str, str | io.IOBase] | None = None) -> FileMap:
309315
"""Class method to make files holder for this image type
310316
311317
Parameters
@@ -338,7 +344,7 @@ def make_file_map(klass, mapping: ty.Mapping[str, str | io.IOBase] | None = None
338344
load = from_filename
339345

340346
@classmethod
341-
def instance_to_filename(klass, img: FileBasedImage, filename: FileSpec):
347+
def instance_to_filename(klass, img: FileBasedImage, filename: FileSpec) -> None:
342348
"""Save `img` in our own format, to name implied by `filename`
343349
344350
This is a class method
@@ -354,28 +360,28 @@ def instance_to_filename(klass, img: FileBasedImage, filename: FileSpec):
354360
img.to_filename(filename)
355361

356362
@classmethod
357-
def from_image(klass, img: FileBasedImage):
363+
def from_image(klass: type[ImgT], img: FileBasedImage) -> ImgT:
358364
"""Class method to create new instance of own class from `img`
359365
360366
Parameters
361367
----------
362-
img : ``spatialimage`` instance
368+
img : ``FileBasedImage`` instance
363369
In fact, an object with the API of ``FileBasedImage``.
364370
365371
Returns
366372
-------
367-
cimg : ``spatialimage`` instance
373+
img : ``FileBasedImage`` instance
368374
Image, of our own class
369375
"""
370-
raise NotImplementedError()
376+
raise NotImplementedError # pragma: no cover
371377

372378
@classmethod
373379
def _sniff_meta_for(
374380
klass,
375381
filename: FileSpec,
376382
sniff_nbytes: int,
377383
sniff: FileSniff | None = None,
378-
):
384+
) -> FileSniff | None:
379385
"""Sniff metadata for image represented by `filename`
380386
381387
Parameters
@@ -425,7 +431,7 @@ def path_maybe_image(
425431
filename: FileSpec,
426432
sniff: FileSniff | None = None,
427433
sniff_max: int = 1024,
428-
):
434+
) -> tuple[bool, FileSniff | None]:
429435
"""Return True if `filename` may be image matching this class
430436
431437
Parameters
@@ -527,14 +533,14 @@ class SerializableImage(FileBasedImage):
527533
"""
528534

529535
@classmethod
530-
def _filemap_from_iobase(klass, io_obj: io.IOBase):
536+
def _filemap_from_iobase(klass, io_obj: io.IOBase) -> FileMap:
531537
"""For single-file image types, make a file map with the correct key"""
532538
if len(klass.files_types) > 1:
533539
raise NotImplementedError('(de)serialization is undefined for multi-file images')
534540
return klass.make_file_map({klass.files_types[0][0]: io_obj})
535541

536542
@classmethod
537-
def from_stream(klass, io_obj: io.IOBase):
543+
def from_stream(klass: type[StreamImgT], io_obj: io.IOBase) -> StreamImgT:
538544
"""Load image from readable IO stream
539545
540546
Convert to BytesIO to enable seeking, if input stream is not seekable
@@ -548,7 +554,7 @@ def from_stream(klass, io_obj: io.IOBase):
548554
io_obj = io.BytesIO(io_obj.read())
549555
return klass.from_file_map(klass._filemap_from_iobase(io_obj))
550556

551-
def to_stream(self, io_obj: io.IOBase, **kwargs):
557+
def to_stream(self, io_obj: io.IOBase, **kwargs) -> None:
552558
r"""Save image to writable IO stream
553559
554560
Parameters
@@ -561,7 +567,7 @@ def to_stream(self, io_obj: io.IOBase, **kwargs):
561567
self.to_file_map(self._filemap_from_iobase(io_obj), **kwargs)
562568

563569
@classmethod
564-
def from_bytes(klass, bytestring: bytes):
570+
def from_bytes(klass: type[StreamImgT], bytestring: bytes) -> StreamImgT:
565571
"""Construct image from a byte string
566572
567573
Class method
@@ -592,7 +598,9 @@ def to_bytes(self, **kwargs) -> bytes:
592598
return bio.getvalue()
593599

594600
@classmethod
595-
def from_url(klass, url: str | request.Request, timeout: float = 5):
601+
def from_url(
602+
klass: type[StreamImgT], url: str | request.Request, timeout: float = 5
603+
) -> StreamImgT:
596604
"""Retrieve and load an image from a URL
597605
598606
Class method

0 commit comments

Comments
 (0)