Skip to content

Commit 5a4f534

Browse files
author
Steve Canny
committed
img: add ImageParts.get_or_add_image_part()
1 parent 35e98da commit 5a4f534

File tree

3 files changed

+124
-4
lines changed

3 files changed

+124
-4
lines changed

docx/package.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from docx.opc.constants import RELATIONSHIP_TYPE as RT
1010
from docx.opc.package import OpcPackage
11+
from docx.parts.image import Image
1112
from docx.shared import lazyproperty
1213

1314

@@ -45,8 +46,8 @@ def _gather_image_parts(self):
4546

4647
class ImageParts(object):
4748
"""
48-
Collection of |ImagePart| instances containing all the image parts in the
49-
package.
49+
Collection of |ImagePart| instances corresponding to each image part in
50+
the package.
5051
"""
5152
def __init__(self):
5253
super(ImageParts, self).__init__()
@@ -67,3 +68,25 @@ def get_or_add_image_part(self, image_descriptor):
6768
*image_descriptor*, newly created if a matching one is not present in
6869
the collection.
6970
"""
71+
image = Image.load(image_descriptor)
72+
matching_image_part = self._get_by_sha1(image.sha1)
73+
if matching_image_part is not None:
74+
return matching_image_part
75+
return self._add_image_part(image)
76+
77+
def _add_image_part(self, image):
78+
"""
79+
Return the image part in this collection having a SHA1 hash matching
80+
*sha1*, or |None| if not found.
81+
"""
82+
raise NotImplementedError
83+
84+
def _get_by_sha1(self, sha1):
85+
"""
86+
Return the image part in this collection having a SHA1 hash matching
87+
*sha1*, or |None| if not found.
88+
"""
89+
for image_part in self._image_parts:
90+
if image_part.sha1 == sha1:
91+
return image_part
92+
return None

docx/parts/image.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@
99
from docx.opc.package import Part
1010

1111

12+
class Image(object):
13+
"""
14+
A helper object that knows how to analyze an image file.
15+
"""
16+
@classmethod
17+
def load(self, image_descriptor):
18+
"""
19+
Return a new |Image| instance loaded from the image file identified
20+
by *image_descriptor*, a path or file-like object.
21+
"""
22+
23+
@property
24+
def sha1(self):
25+
"""
26+
SHA1 hash digest of the image blob
27+
"""
28+
raise NotImplementedError
29+
30+
1231
class ImagePart(Part):
1332
"""
1433
An image part. Corresponds to the target part of a relationship with type
@@ -35,6 +54,13 @@ def height(self):
3554
"""
3655
raise NotImplementedError
3756

57+
@property
58+
def sha1(self):
59+
"""
60+
SHA1 hash digest of the blob of this image part.
61+
"""
62+
raise NotImplementedError
63+
3864
@property
3965
def width(self):
4066
"""

tests/test_package.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,84 @@
66

77
from __future__ import absolute_import, print_function, unicode_literals
88

9-
from docx.package import Package
9+
import pytest
1010

11-
from .unitutil import docx_path
11+
from docx.package import ImageParts, Package
12+
from docx.parts.image import Image, ImagePart
13+
14+
from .unitutil import class_mock, docx_path, instance_mock, method_mock
1215

1316

1417
class DescribePackage(object):
1518

1619
def it_gathers_package_image_parts_after_unmarshalling(self):
1720
package = Package.open(docx_path('having-images'))
1821
assert len(package.image_parts) == 3
22+
23+
24+
class DescribeImageParts(object):
25+
26+
def it_can_get_a_matching_image_part(self, get_image_part_fixture):
27+
image_parts, image_descriptor, image_part_ = get_image_part_fixture
28+
image_part = image_parts.get_or_add_image_part(image_descriptor)
29+
assert image_part is image_part_
30+
31+
def it_can_add_a_new_image_part(self, add_image_part_fixture):
32+
image_parts, image_descriptor, image_, image_part_ = (
33+
add_image_part_fixture
34+
)
35+
image_part = image_parts.get_or_add_image_part(image_descriptor)
36+
image_parts._add_image_part.assert_called_once_with(image_)
37+
assert image_part is image_part_
38+
39+
# fixtures -------------------------------------------------------
40+
41+
@pytest.fixture
42+
def _add_image_part_(self, request, new_image_part_):
43+
return method_mock(
44+
request, ImageParts, '_add_image_part',
45+
return_value=new_image_part_
46+
)
47+
48+
@pytest.fixture
49+
def add_image_part_fixture(
50+
self, Image_, _add_image_part_, image_descriptor_, image_,
51+
new_image_part_,):
52+
image_parts = ImageParts()
53+
return image_parts, image_descriptor_, image_, new_image_part_
54+
55+
@pytest.fixture
56+
def get_image_part_fixture(self, Image_, image_part_, image_descriptor_):
57+
image_parts = ImageParts()
58+
image_parts.append(image_part_)
59+
return image_parts, image_descriptor_, image_part_
60+
61+
@pytest.fixture
62+
def Image_(self, request, image_):
63+
Image_ = class_mock(request, 'docx.package.Image')
64+
Image_.load.return_value = image_
65+
return Image_
66+
67+
@pytest.fixture
68+
def image_(self, request, sha1):
69+
image_ = instance_mock(request, Image)
70+
image_.sha1 = sha1
71+
return image_
72+
73+
@pytest.fixture
74+
def image_descriptor_(self, request):
75+
return instance_mock(request, str)
76+
77+
@pytest.fixture
78+
def image_part_(self, request, sha1):
79+
image_part_ = instance_mock(request, ImagePart)
80+
image_part_.sha1 = sha1
81+
return image_part_
82+
83+
@pytest.fixture
84+
def new_image_part_(self, request):
85+
return instance_mock(request, ImagePart)
86+
87+
@pytest.fixture
88+
def sha1(self):
89+
return 'F008AH'

0 commit comments

Comments
 (0)