24
24
FileMap = ty .Mapping [str , FileHolder ]
25
25
FileSniff = ty .Tuple [bytes , str ]
26
26
27
+ ImgT = ty .TypeVar ('ImgT' , bound = 'FileBasedImage' )
28
+ HdrT = ty .TypeVar ('HdrT' , bound = 'FileBasedHeader' )
29
+
30
+ StreamImgT = ty .TypeVar ('StreamImgT' , bound = 'SerializableImage' )
31
+
27
32
28
33
class ImageFileError (Exception ):
29
34
pass
@@ -33,7 +38,7 @@ class FileBasedHeader:
33
38
"""Template class to implement header protocol"""
34
39
35
40
@classmethod
36
- def from_header (klass , header = None ):
41
+ def from_header (klass : type [ HdrT ] , header : FileBasedHeader | ty . Mapping | None = None ) -> HdrT :
37
42
if header is None :
38
43
return klass ()
39
44
# I can't do isinstance here because it is not necessarily true
@@ -47,19 +52,19 @@ def from_header(klass, header=None):
47
52
)
48
53
49
54
@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
52
57
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
55
60
56
- def __eq__ (self , other ) :
57
- raise NotImplementedError
61
+ def __eq__ (self , other : object ) -> bool :
62
+ raise NotImplementedError # pragma: no cover
58
63
59
- def __ne__ (self , other ) :
64
+ def __ne__ (self , other : object ) -> bool :
60
65
return not self == other
61
66
62
- def copy (self ) -> FileBasedHeader :
67
+ def copy (self : HdrT ) -> HdrT :
63
68
"""Copy object to independent representation
64
69
65
70
The copy should not be affected by any changes to the original
@@ -153,6 +158,7 @@ class FileBasedImage:
153
158
"""
154
159
155
160
header_class : Type [FileBasedHeader ] = FileBasedHeader
161
+ _header : FileBasedHeader
156
162
_meta_sniff_len : int = 0
157
163
files_types : tuple [tuple [str , str | None ], ...] = (('image' , None ),)
158
164
valid_exts : tuple [str , ...] = ()
@@ -186,7 +192,7 @@ def __init__(
186
192
self ._header = self .header_class .from_header (header )
187
193
if extra is None :
188
194
extra = {}
189
- self .extra = extra
195
+ self .extra = dict ( extra )
190
196
191
197
if file_map is None :
192
198
file_map = self .__class__ .make_file_map ()
@@ -196,7 +202,7 @@ def __init__(
196
202
def header (self ) -> FileBasedHeader :
197
203
return self ._header
198
204
199
- def __getitem__ (self , key ):
205
+ def __getitem__ (self , key ) -> None :
200
206
"""No slicing or dictionary interface for images"""
201
207
raise TypeError ('Cannot slice image objects.' )
202
208
@@ -221,7 +227,7 @@ def get_filename(self) -> str | None:
221
227
characteristic_type = self .files_types [0 ][0 ]
222
228
return self .file_map [characteristic_type ].filename
223
229
224
- def set_filename (self , filename : str ):
230
+ def set_filename (self , filename : str ) -> None :
225
231
"""Sets the files in the object from a given filename
226
232
227
233
The different image formats may check whether the filename has
@@ -239,16 +245,16 @@ def set_filename(self, filename: str):
239
245
self .file_map = self .__class__ .filespec_to_file_map (filename )
240
246
241
247
@classmethod
242
- def from_filename (klass , filename : FileSpec ):
248
+ def from_filename (klass : type [ ImgT ] , filename : FileSpec ) -> ImgT :
243
249
file_map = klass .filespec_to_file_map (filename )
244
250
return klass .from_file_map (file_map )
245
251
246
252
@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
249
255
250
256
@classmethod
251
- def filespec_to_file_map (klass , filespec : FileSpec ):
257
+ def filespec_to_file_map (klass , filespec : FileSpec ) -> FileMap :
252
258
"""Make `file_map` for this class from filename `filespec`
253
259
254
260
Class method
@@ -282,7 +288,7 @@ def filespec_to_file_map(klass, filespec: FileSpec):
282
288
file_map [key ] = FileHolder (filename = fname )
283
289
return file_map
284
290
285
- def to_filename (self , filename : FileSpec , ** kwargs ):
291
+ def to_filename (self , filename : FileSpec , ** kwargs ) -> None :
286
292
r"""Write image to files implied by filename string
287
293
288
294
Parameters
@@ -301,11 +307,11 @@ def to_filename(self, filename: FileSpec, **kwargs):
301
307
self .file_map = self .filespec_to_file_map (filename )
302
308
self .to_file_map (** kwargs )
303
309
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
306
312
307
313
@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 :
309
315
"""Class method to make files holder for this image type
310
316
311
317
Parameters
@@ -338,7 +344,7 @@ def make_file_map(klass, mapping: ty.Mapping[str, str | io.IOBase] | None = None
338
344
load = from_filename
339
345
340
346
@classmethod
341
- def instance_to_filename (klass , img : FileBasedImage , filename : FileSpec ):
347
+ def instance_to_filename (klass , img : FileBasedImage , filename : FileSpec ) -> None :
342
348
"""Save `img` in our own format, to name implied by `filename`
343
349
344
350
This is a class method
@@ -354,28 +360,28 @@ def instance_to_filename(klass, img: FileBasedImage, filename: FileSpec):
354
360
img .to_filename (filename )
355
361
356
362
@classmethod
357
- def from_image (klass , img : FileBasedImage ):
363
+ def from_image (klass : type [ ImgT ] , img : FileBasedImage ) -> ImgT :
358
364
"""Class method to create new instance of own class from `img`
359
365
360
366
Parameters
361
367
----------
362
- img : ``spatialimage `` instance
368
+ img : ``FileBasedImage `` instance
363
369
In fact, an object with the API of ``FileBasedImage``.
364
370
365
371
Returns
366
372
-------
367
- cimg : ``spatialimage `` instance
373
+ img : ``FileBasedImage `` instance
368
374
Image, of our own class
369
375
"""
370
- raise NotImplementedError ()
376
+ raise NotImplementedError # pragma: no cover
371
377
372
378
@classmethod
373
379
def _sniff_meta_for (
374
380
klass ,
375
381
filename : FileSpec ,
376
382
sniff_nbytes : int ,
377
383
sniff : FileSniff | None = None ,
378
- ):
384
+ ) -> FileSniff | None :
379
385
"""Sniff metadata for image represented by `filename`
380
386
381
387
Parameters
@@ -425,7 +431,7 @@ def path_maybe_image(
425
431
filename : FileSpec ,
426
432
sniff : FileSniff | None = None ,
427
433
sniff_max : int = 1024 ,
428
- ):
434
+ ) -> tuple [ bool , FileSniff | None ] :
429
435
"""Return True if `filename` may be image matching this class
430
436
431
437
Parameters
@@ -527,14 +533,14 @@ class SerializableImage(FileBasedImage):
527
533
"""
528
534
529
535
@classmethod
530
- def _filemap_from_iobase (klass , io_obj : io .IOBase ):
536
+ def _filemap_from_iobase (klass , io_obj : io .IOBase ) -> FileMap :
531
537
"""For single-file image types, make a file map with the correct key"""
532
538
if len (klass .files_types ) > 1 :
533
539
raise NotImplementedError ('(de)serialization is undefined for multi-file images' )
534
540
return klass .make_file_map ({klass .files_types [0 ][0 ]: io_obj })
535
541
536
542
@classmethod
537
- def from_stream (klass , io_obj : io .IOBase ):
543
+ def from_stream (klass : type [ StreamImgT ] , io_obj : io .IOBase ) -> StreamImgT :
538
544
"""Load image from readable IO stream
539
545
540
546
Convert to BytesIO to enable seeking, if input stream is not seekable
@@ -548,7 +554,7 @@ def from_stream(klass, io_obj: io.IOBase):
548
554
io_obj = io .BytesIO (io_obj .read ())
549
555
return klass .from_file_map (klass ._filemap_from_iobase (io_obj ))
550
556
551
- def to_stream (self , io_obj : io .IOBase , ** kwargs ):
557
+ def to_stream (self , io_obj : io .IOBase , ** kwargs ) -> None :
552
558
r"""Save image to writable IO stream
553
559
554
560
Parameters
@@ -561,7 +567,7 @@ def to_stream(self, io_obj: io.IOBase, **kwargs):
561
567
self .to_file_map (self ._filemap_from_iobase (io_obj ), ** kwargs )
562
568
563
569
@classmethod
564
- def from_bytes (klass , bytestring : bytes ):
570
+ def from_bytes (klass : type [ StreamImgT ] , bytestring : bytes ) -> StreamImgT :
565
571
"""Construct image from a byte string
566
572
567
573
Class method
@@ -592,7 +598,9 @@ def to_bytes(self, **kwargs) -> bytes:
592
598
return bio .getvalue ()
593
599
594
600
@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 :
596
604
"""Retrieve and load an image from a URL
597
605
598
606
Class method
0 commit comments