Skip to content

Commit 002e37f

Browse files
committed
hdr: add BaseStoryPart.get_or_add_image()
Also extract Package.get_or_add_image_part() to avoid BaseStoryPart and others having to know about ImageParts.
1 parent 7f32a65 commit 002e37f

File tree

6 files changed

+62
-41
lines changed

6 files changed

+62
-41
lines changed

docx/package.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ def after_unmarshal(self):
2222
"""
2323
self._gather_image_parts()
2424

25+
def get_or_add_image_part(self, image_descriptor):
26+
"""Return |ImagePart| containing image specified by *image_descriptor*.
27+
28+
The image-part is newly created if a matching one is not already present in the
29+
collection.
30+
"""
31+
return self.image_parts.get_or_add_image_part(image_descriptor)
32+
2533
@lazyproperty
2634
def image_parts(self):
2735
"""|ImageParts| collection object for this package."""

docx/parts/document.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,6 @@ def footer_part(self, rId):
5959
"""Return |FooterPart| related by *rId*."""
6060
return self.related_parts[rId]
6161

62-
def get_or_add_image(self, image_descriptor):
63-
"""
64-
Return an (rId, image) 2-tuple for the image identified by
65-
*image_descriptor*. *image* is an |Image| instance providing access
66-
to the properties of the image, such as dimensions and image type.
67-
*rId* is the key for the relationship between this document part and
68-
the image part, reused if already present, newly created if not.
69-
"""
70-
image_part = self._package.image_parts.get_or_add_image_part(
71-
image_descriptor
72-
)
73-
rId = self.relate_to(image_part, RT.IMAGE)
74-
return rId, image_part.image
75-
7662
def get_style(self, style_id, style_type):
7763
"""
7864
Return the style in this document matching *style_id*. Returns the

docx/parts/story.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import absolute_import, division, print_function, unicode_literals
66

7+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
78
from docx.opc.part import XmlPart
89
from docx.oxml.shape import CT_Inline
910
from docx.shared import lazyproperty
@@ -25,7 +26,9 @@ def get_or_add_image(self, image_descriptor):
2526
*image* is an |Image| instance providing access to the properties of the image,
2627
such as dimensions and image type.
2728
"""
28-
raise NotImplementedError
29+
image_part = self._package.get_or_add_image_part(image_descriptor)
30+
rId = self.relate_to(image_part, RT.IMAGE)
31+
return rId, image_part.image
2932

3033
def get_style(self, style_id, style_type):
3134
"""Return the style in this document matching *style_id*.

tests/parts/test_document.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
import pytest
88

99
from docx.enum.style import WD_STYLE_TYPE
10-
from docx.image.image import Image
1110
from docx.opc.constants import RELATIONSHIP_TYPE as RT
1211
from docx.opc.coreprops import CoreProperties
1312
from docx.package import Package
1413
from docx.parts.document import DocumentPart
1514
from docx.parts.hdrftr import FooterPart, HeaderPart
16-
from docx.parts.image import ImagePart
1715
from docx.parts.numbering import NumberingPart
1816
from docx.parts.settings import SettingsPart
1917
from docx.parts.styles import StylesPart
@@ -88,21 +86,6 @@ def it_can_save_the_package_to_a_file(self, save_fixture):
8886
document.save(file_)
8987
document._package.save.assert_called_once_with(file_)
9088

91-
def it_can_get_or_add_an_image(self, package_, image_part_, image_, relate_to_):
92-
path = 'foobar.png'
93-
package_.image_parts.get_or_add_image_part.return_value = image_part_
94-
relate_to_.return_value = "rId42"
95-
image_part_.image = image_
96-
document_part = DocumentPart(None, None, None, package_)
97-
98-
rId, image = document_part.get_or_add_image(path)
99-
100-
image_parts = document_part._package.image_parts
101-
image_parts.get_or_add_image_part.assert_called_once_with(path)
102-
relate_to_.assert_called_once_with(document_part, image_part_, RT.IMAGE)
103-
assert rId == "rId42"
104-
assert image is image_
105-
10689
def it_provides_access_to_the_document_settings(self, settings_fixture):
10790
document_part, settings_ = settings_fixture
10891
settings = document_part.settings
@@ -307,14 +290,6 @@ def HeaderPart_(self, request):
307290
def header_part_(self, request):
308291
return instance_mock(request, HeaderPart)
309292

310-
@pytest.fixture
311-
def image_(self, request):
312-
return instance_mock(request, Image)
313-
314-
@pytest.fixture
315-
def image_part_(self, request):
316-
return instance_mock(request, ImagePart)
317-
318293
@pytest.fixture
319294
def InlineShapes_(self, request):
320295
return class_mock(request, 'docx.parts.document.InlineShapes')

tests/parts/test_story.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
from docx.enum.style import WD_STYLE_TYPE
1010
from docx.image.image import Image
11+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
1112
from docx.package import Package
1213
from docx.parts.document import DocumentPart
14+
from docx.parts.image import ImagePart
1315
from docx.parts.story import BaseStoryPart
1416
from docx.styles.style import BaseStyle
1517

@@ -19,6 +21,19 @@
1921

2022
class DescribeBaseStoryPart(object):
2123

24+
def it_can_get_or_add_an_image(self, package_, image_part_, image_, relate_to_):
25+
package_.get_or_add_image_part.return_value = image_part_
26+
relate_to_.return_value = "rId42"
27+
image_part_.image = image_
28+
story_part = BaseStoryPart(None, None, None, package_)
29+
30+
rId, image = story_part.get_or_add_image("image.png")
31+
32+
package_.get_or_add_image_part.assert_called_once_with("image.png")
33+
relate_to_.assert_called_once_with(story_part, image_part_, RT.IMAGE)
34+
assert rId == "rId42"
35+
assert image is image_
36+
2237
def it_can_get_a_style_by_id_and_type(
2338
self, _document_part_prop_, document_part_, style_
2439
):
@@ -86,6 +101,10 @@ def get_or_add_image_(self, request):
86101
def image_(self, request):
87102
return instance_mock(request, Image)
88103

104+
@pytest.fixture
105+
def image_part_(self, request):
106+
return instance_mock(request, ImagePart)
107+
89108
@pytest.fixture
90109
def next_id_prop_(self, request):
91110
return property_mock(request, BaseStoryPart, "next_id")
@@ -94,6 +113,10 @@ def next_id_prop_(self, request):
94113
def package_(self, request):
95114
return instance_mock(request, Package)
96115

116+
@pytest.fixture
117+
def relate_to_(self, request):
118+
return method_mock(request, BaseStoryPart, "relate_to")
119+
97120
@pytest.fixture
98121
def style_(self, request):
99122
return instance_mock(request, BaseStyle)

tests/test_package.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,44 @@
1212
from docx.parts.image import ImagePart
1313

1414
from .unitutil.file import docx_path
15-
from .unitutil.mock import class_mock, instance_mock, method_mock
15+
from .unitutil.mock import class_mock, instance_mock, method_mock, property_mock
1616

1717

1818
class DescribePackage(object):
1919

20+
def it_can_get_or_add_an_image_part_containing_a_specified_image(
21+
self, image_parts_prop_, image_parts_, image_part_
22+
):
23+
image_parts_prop_.return_value = image_parts_
24+
image_parts_.get_or_add_image_part.return_value = image_part_
25+
package = Package()
26+
27+
image_part = package.get_or_add_image_part("image.png")
28+
29+
image_parts_.get_or_add_image_part.assert_called_once_with("image.png")
30+
assert image_part is image_part_
31+
2032
def it_gathers_package_image_parts_after_unmarshalling(self):
2133
package = Package.open(docx_path('having-images'))
2234
image_parts = package.image_parts
2335
assert len(image_parts) == 3
2436
for image_part in image_parts:
2537
assert isinstance(image_part, ImagePart)
2638

39+
# fixture components ---------------------------------------------
40+
41+
@pytest.fixture
42+
def image_part_(self, request):
43+
return instance_mock(request, ImagePart)
44+
45+
@pytest.fixture
46+
def image_parts_(self, request):
47+
return instance_mock(request, ImageParts)
48+
49+
@pytest.fixture
50+
def image_parts_prop_(self, request):
51+
return property_mock(request, Package, "image_parts")
52+
2753

2854
class DescribeImageParts(object):
2955

0 commit comments

Comments
 (0)