Skip to content

Commit 38afcb6

Browse files
author
Steve Canny
committed
img: add .default_ext to image header classes
* abstract property on BaseImageHeader * removed a few lines of dead code from png.py
1 parent a8faaeb commit 38afcb6

12 files changed

Lines changed: 87 additions & 14 deletions

File tree

docx/image/bmp.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ def content_type(self):
3838
"""
3939
return MIME_TYPE.BMP
4040

41+
@property
42+
def default_ext(self):
43+
"""
44+
Default filename extension, always 'bmp' for BMP images.
45+
"""
46+
return 'bmp'
47+
4148
@staticmethod
4249
def _dpi(px_per_meter):
4350
"""

docx/image/gif.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ def content_type(self):
3131
"""
3232
return MIME_TYPE.GIF
3333

34+
@property
35+
def default_ext(self):
36+
"""
37+
Default filename extension, always 'gif' for GIF images.
38+
"""
39+
return 'gif'
40+
3441
@classmethod
3542
def _dimensions_from_stream(cls, stream):
3643
stream.seek(6)

docx/image/image.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ def _from_stream(cls, stream, blob, filename=None):
131131
format of the image in *stream*.
132132
"""
133133
image_header = _ImageHeaderFactory(stream)
134+
if filename is None:
135+
filename = 'image.%s' % image_header.default_ext
134136
return cls(blob, filename, image_header)
135137

136138

@@ -175,6 +177,18 @@ def content_type(self):
175177
)
176178
raise NotImplementedError(msg)
177179

180+
@property
181+
def default_ext(self):
182+
"""
183+
Default filename extension for images of this type. An abstract
184+
property definition, must be implemented by all subclasses.
185+
"""
186+
msg = (
187+
'default_ext property must be implemented by all subclasses of '
188+
'BaseImageHeader'
189+
)
190+
raise NotImplementedError(msg)
191+
178192
@property
179193
def px_width(self):
180194
"""

docx/image/jpeg.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ def content_type(self):
2626
"""
2727
return MIME_TYPE.JPEG
2828

29+
@property
30+
def default_ext(self):
31+
"""
32+
Default filename extension, always 'jpg' for JPG images.
33+
"""
34+
return 'jpg'
35+
2936

3037
class Exif(Jpeg):
3138
"""

docx/image/png.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88
from .image import BaseImageHeader
99

1010

11-
_CHUNK_TYPE_IHDR = 'IHDR'
12-
_CHUNK_TYPE_pHYs = 'pHYs'
13-
_CHUNK_TYPE_IEND = 'IEND'
14-
15-
1611
class Png(BaseImageHeader):
1712
"""
1813
Image header parser for PNG images
@@ -25,6 +20,13 @@ def content_type(self):
2520
"""
2621
return MIME_TYPE.PNG
2722

23+
@property
24+
def default_ext(self):
25+
"""
26+
Default filename extension, always 'png' for PNG images.
27+
"""
28+
return 'png'
29+
2830
@classmethod
2931
def from_stream(cls, stream):
3032
"""

docx/image/tiff.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ def content_type(self):
2020
"""
2121
return MIME_TYPE.TIFF
2222

23+
@property
24+
def default_ext(self):
25+
"""
26+
Default filename extension, always 'tiff' for TIFF images.
27+
"""
28+
return 'tiff'
29+
2330
@classmethod
2431
def from_stream(cls, stream):
2532
"""

tests/image/test_bmp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def it_knows_its_content_type(self):
2727
bmp = Bmp(None, None, None, None)
2828
assert bmp.content_type == MIME_TYPE.BMP
2929

30+
def it_knows_its_default_ext(self):
31+
bmp = Bmp(None, None, None, None)
32+
assert bmp.default_ext == 'bmp'
33+
3034
# fixtures -------------------------------------------------------
3135

3236
@pytest.fixture

tests/image/test_gif.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def it_knows_its_content_type(self):
2727
gif = Gif(None, None, None, None)
2828
assert gif.content_type == MIME_TYPE.GIF
2929

30+
def it_knows_its_default_ext(self):
31+
gif = Gif(None, None, None, None)
32+
assert gif.default_ext == 'gif'
33+
3034
# fixtures -------------------------------------------------------
3135

3236
@pytest.fixture

tests/image/test_image.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,16 @@ def it_can_construct_from_an_image_file_like(self, from_filelike_fixture):
5050

5151
def it_can_construct_from_an_image_stream(self, from_stream_fixture):
5252
# fixture ----------------------
53-
stream_, blob_, filename_ = from_stream_fixture[:3]
53+
stream_, blob_, filename_in = from_stream_fixture[:3]
5454
_ImageHeaderFactory_, image_header_ = from_stream_fixture[3:5]
55-
Image__init_ = from_stream_fixture[5]
55+
Image__init_, filename_out = from_stream_fixture[5:]
5656
# exercise ---------------------
57-
image = Image._from_stream(stream_, blob_, filename_)
57+
image = Image._from_stream(stream_, blob_, filename_in)
5858
# verify -----------------------
5959
_ImageHeaderFactory_.assert_called_once_with(stream_)
60-
Image__init_.assert_called_once_with(blob_, filename_, image_header_)
60+
Image__init_.assert_called_once_with(
61+
blob_, filename_out, image_header_
62+
)
6163
assert isinstance(image, Image)
6264

6365
def it_provides_access_to_the_image_blob(self):
@@ -153,13 +155,15 @@ def from_path_fixture(self, _from_stream_, BytesIO_, stream_, image_):
153155
blob = f.read()
154156
return image_path, _from_stream_, stream_, blob, filename, image_
155157

156-
@pytest.fixture
158+
@pytest.fixture(params=['foobar.png', None])
157159
def from_stream_fixture(
158-
self, stream_, blob_, filename_, _ImageHeaderFactory_,
160+
self, request, stream_, blob_, _ImageHeaderFactory_,
159161
image_header_, Image__init_):
162+
filename_in = request.param
163+
filename_out = 'image.png' if filename_in is None else filename_in
160164
return (
161-
stream_, blob_, filename_, _ImageHeaderFactory_, image_header_,
162-
Image__init_
165+
stream_, blob_, filename_in, _ImageHeaderFactory_, image_header_,
166+
Image__init_, filename_out
163167
)
164168

165169
@pytest.fixture
@@ -181,7 +185,7 @@ def _ImageHeaderFactory_(self, request, image_header_):
181185

182186
@pytest.fixture
183187
def image_header_(self, request):
184-
return instance_mock(request, BaseImageHeader)
188+
return instance_mock(request, BaseImageHeader, default_ext='png')
185189

186190
@pytest.fixture
187191
def Image__init_(self, request):
@@ -233,6 +237,11 @@ def it_defines_content_type_as_an_abstract_property(self):
233237
with pytest.raises(NotImplementedError):
234238
base_image_header.content_type
235239

240+
def it_defines_default_ext_as_an_abstract_property(self):
241+
base_image_header = BaseImageHeader(None, None, None, None)
242+
with pytest.raises(NotImplementedError):
243+
base_image_header.default_ext
244+
236245
def it_knows_the_image_dimensions(self):
237246
px_width, px_height = 42, 24
238247
image_header = BaseImageHeader(px_width, px_height, None, None)

tests/image/test_jpeg.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ def it_knows_its_content_type(self):
3030
jpeg = Jpeg(None, None, None, None)
3131
assert jpeg.content_type == MIME_TYPE.JPEG
3232

33+
def it_knows_its_default_ext(self):
34+
jpeg = Jpeg(None, None, None, None)
35+
assert jpeg.default_ext == 'jpg'
36+
3337
class DescribeExif(object):
3438

3539
def it_can_construct_from_an_exif_stream(self, from_exif_fixture):

0 commit comments

Comments
 (0)