Skip to content

Commit ea07f47

Browse files
committed
rfctr: make PackageReader a value object
Use @lazyproperty in place of pre-computed instance variables.
1 parent 81102ff commit ea07f47

File tree

4 files changed

+45
-150
lines changed

4 files changed

+45
-150
lines changed

pptx/compat/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import collections
88

99
try:
10+
Container = collections.abc.Container
1011
Mapping = collections.abc.Mapping
1112
Sequence = collections.abc.Sequence
1213
except AttributeError:
14+
Container = collections.Container
1315
Mapping = collections.Mapping
1416
Sequence = collections.Sequence
1517

pptx/opc/package.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def _load(self):
184184
@lazyproperty
185185
def _package_reader(self):
186186
"""|PackageReader| object providing access to package-items in pkg_file."""
187-
return PackageReader.from_file(self._pkg_file)
187+
return PackageReader(self._pkg_file)
188188

189189
@lazyproperty
190190
def _parts(self):

pptx/opc/serialized.py

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,29 @@
55
import os
66
import zipfile
77

8-
from pptx.compat import is_string
8+
from pptx.compat import Container, is_string
99
from pptx.exceptions import PackageNotFoundError
1010
from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TARGET_MODE as RTM
1111
from pptx.opc.oxml import CT_Types, parse_xml, serialize_part_xml
1212
from pptx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI, PackURI
1313
from pptx.opc.shared import CaseInsensitiveDict
1414
from pptx.opc.spec import default_content_types
15+
from pptx.util import lazyproperty
1516

1617

17-
class PackageReader(object):
18-
"""
19-
Provides access to the contents of a zip-format OPC package via its
20-
:attr:`serialized_parts` and :attr:`pkg_srels` attributes.
18+
class PackageReader(Container):
19+
"""Provides access to package-parts of an OPC package with dict semantics.
20+
21+
The package may be in zip-format (a .pptx file) or expanded into a directory
22+
structure, perhaps by unzipping a .pptx file.
2123
"""
2224

23-
def __init__(self, content_types, pkg_srels, sparts):
24-
super(PackageReader, self).__init__()
25-
self._pkg_srels = pkg_srels
26-
self._sparts = sparts
25+
def __init__(self, pkg_file):
26+
self._pkg_file = pkg_file
2727

28-
@staticmethod
29-
def from_file(pkg_file):
30-
"""
31-
Return a |PackageReader| instance loaded with contents of *pkg_file*.
32-
"""
33-
phys_reader = _PhysPkgReader(pkg_file)
34-
content_types = _ContentTypeMap.from_xml(phys_reader.content_types_xml)
35-
pkg_srels = PackageReader._srels_for(phys_reader, PACKAGE_URI)
36-
sparts = PackageReader._load_serialized_parts(
37-
phys_reader, pkg_srels, content_types
38-
)
39-
phys_reader.close()
40-
return PackageReader(content_types, pkg_srels, sparts)
28+
def __contains__(self, pack_uri):
29+
"""Return True when part identified by `pack_uri` is present in package."""
30+
return pack_uri in self._blob_reader
4131

4232
def iter_sparts(self):
4333
"""
@@ -71,23 +61,40 @@ def rels_xml_for(self, pkg_file, partname):
7161
phys_reader.close()
7262
return rels_xml
7363

74-
@staticmethod
75-
def _load_serialized_parts(phys_reader, pkg_srels, content_types):
76-
"""
64+
@lazyproperty
65+
def _content_types(self):
66+
"""Filty temporary hack during refactoring."""
67+
phys_reader = _PhysPkgReader(self._pkg_file)
68+
return _ContentTypeMap.from_xml(phys_reader.content_types_xml)
69+
phys_reader.close()
70+
71+
@lazyproperty
72+
def _pkg_srels(self):
73+
"""Filty temporary hack during refactoring."""
74+
phys_reader = _PhysPkgReader(self._pkg_file)
75+
pkg_srels = self._srels_for(phys_reader, PACKAGE_URI)
76+
phys_reader.close()
77+
return pkg_srels
78+
79+
@lazyproperty
80+
def _sparts(self):
81+
"""Filty temporary hack during refactoring.
82+
7783
Return a list of |_SerializedPart| instances corresponding to the
7884
parts in *phys_reader* accessible by walking the relationship graph
79-
starting with *pkg_srels*.
85+
starting with `pkg_srels`.
8086
"""
87+
phys_reader = _PhysPkgReader(self._pkg_file)
8188
sparts = []
82-
part_walker = PackageReader._walk_phys_parts(phys_reader, pkg_srels)
89+
part_walker = self._walk_phys_parts(phys_reader, self._pkg_srels)
8390
for partname, blob, srels in part_walker:
84-
content_type = content_types[partname]
91+
content_type = self._content_types[partname]
8592
spart = _SerializedPart(partname, content_type, blob, srels)
8693
sparts.append(spart)
94+
phys_reader.close()
8795
return tuple(sparts)
8896

89-
@staticmethod
90-
def _srels_for(phys_reader, source_uri):
97+
def _srels_for(self, phys_reader, source_uri):
9198
"""
9299
Return |_SerializedRelationshipCollection| instance populated with
93100
relationships for source identified by *source_uri*.
@@ -97,8 +104,7 @@ def _srels_for(phys_reader, source_uri):
97104
source_uri.baseURI, rels_xml
98105
)
99106

100-
@staticmethod
101-
def _walk_phys_parts(phys_reader, srels, visited_partnames=None):
107+
def _walk_phys_parts(self, phys_reader, srels, visited_partnames=None):
102108
"""
103109
Generate a 3-tuple `(partname, blob, srels)` for each of the parts in
104110
*phys_reader* by walking the relationship graph rooted at srels.
@@ -112,10 +118,10 @@ def _walk_phys_parts(phys_reader, srels, visited_partnames=None):
112118
if partname in visited_partnames:
113119
continue
114120
visited_partnames.append(partname)
115-
part_srels = PackageReader._srels_for(phys_reader, partname)
121+
part_srels = self._srels_for(phys_reader, partname)
116122
blob = phys_reader.blob_for(partname)
117123
yield (partname, blob, part_srels)
118-
for partname, blob, srels in PackageReader._walk_phys_parts(
124+
for partname, blob, srels in self._walk_phys_parts(
119125
phys_reader, part_srels, visited_partnames
120126
):
121127
yield (partname, blob, srels)

tests/opc/test_serialized.py

Lines changed: 2 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
call,
4141
class_mock,
4242
function_mock,
43-
initializer_mock,
4443
instance_mock,
4544
loose_mock,
4645
method_mock,
@@ -56,92 +55,6 @@
5655
class DescribePackageReader(object):
5756
"""Unit-test suite for `pptx.opc.serialized.PackageReader` objects."""
5857

59-
def it_can_construct_from_pkg_file(
60-
self, init, _PhysPkgReader_, from_xml, _srels_for, _load_serialized_parts
61-
):
62-
63-
phys_reader = _PhysPkgReader_.return_value
64-
content_types = from_xml.return_value
65-
pkg_srels = _srels_for.return_value
66-
sparts = _load_serialized_parts.return_value
67-
pkg_file = Mock(name="pkg_file")
68-
69-
pkg_reader = PackageReader.from_file(pkg_file)
70-
71-
_PhysPkgReader_.assert_called_once_with(pkg_file)
72-
from_xml.assert_called_once_with(phys_reader.content_types_xml)
73-
_srels_for.assert_called_once_with(phys_reader, "/")
74-
_load_serialized_parts.assert_called_once_with(
75-
phys_reader, pkg_srels, content_types
76-
)
77-
phys_reader.close.assert_called_once_with()
78-
init.assert_called_once_with(pkg_reader, content_types, pkg_srels, sparts)
79-
assert isinstance(pkg_reader, PackageReader)
80-
81-
def it_can_iterate_over_the_serialized_parts(self):
82-
# mockery ----------------------
83-
partname, content_type, blob = ("part/name.xml", "app/vnd.type", "<Part_1/>")
84-
spart = Mock(
85-
name="spart", partname=partname, content_type=content_type, blob=blob
86-
)
87-
pkg_reader = PackageReader(None, None, [spart])
88-
iter_count = 0
89-
# exercise ---------------------
90-
for retval in pkg_reader.iter_sparts():
91-
iter_count += 1
92-
# verify -----------------------
93-
assert retval == (partname, content_type, blob)
94-
assert iter_count == 1
95-
96-
def it_can_iterate_over_all_the_srels(self):
97-
# mockery ----------------------
98-
pkg_srels = ["srel1", "srel2"]
99-
sparts = [
100-
Mock(name="spart1", partname="pn1", srels=["srel3", "srel4"]),
101-
Mock(name="spart2", partname="pn2", srels=["srel5", "srel6"]),
102-
]
103-
pkg_reader = PackageReader(None, pkg_srels, sparts)
104-
# exercise ---------------------
105-
generated_tuples = [t for t in pkg_reader.iter_srels()]
106-
# verify -----------------------
107-
expected_tuples = [
108-
("/", "srel1"),
109-
("/", "srel2"),
110-
("pn1", "srel3"),
111-
("pn1", "srel4"),
112-
("pn2", "srel5"),
113-
("pn2", "srel6"),
114-
]
115-
assert generated_tuples == expected_tuples
116-
117-
def it_can_load_serialized_parts(self, _SerializedPart_, _walk_phys_parts):
118-
# test data --------------------
119-
test_data = (
120-
("/part/name1.xml", "app/vnd.type_1", "<Part_1/>", "srels_1"),
121-
("/part/name2.xml", "app/vnd.type_2", "<Part_2/>", "srels_2"),
122-
)
123-
iter_vals = [(t[0], t[2], t[3]) for t in test_data]
124-
content_types = dict((t[0], t[1]) for t in test_data)
125-
# mockery ----------------------
126-
phys_reader = Mock(name="phys_reader")
127-
pkg_srels = Mock(name="pkg_srels")
128-
_walk_phys_parts.return_value = iter_vals
129-
_SerializedPart_.side_effect = expected_sparts = (
130-
Mock(name="spart_1"),
131-
Mock(name="spart_2"),
132-
)
133-
# exercise ---------------------
134-
retval = PackageReader._load_serialized_parts(
135-
phys_reader, pkg_srels, content_types
136-
)
137-
# verify -----------------------
138-
expected_calls = [
139-
call("/part/name1.xml", "app/vnd.type_1", "<Part_1/>", "srels_1"),
140-
call("/part/name2.xml", "app/vnd.type_2", "<Part_2/>", "srels_2"),
141-
]
142-
assert _SerializedPart_.call_args_list == expected_calls
143-
assert retval == expected_sparts
144-
14558
def it_can_walk_phys_pkg_parts(self, _srels_for):
14659
# test data --------------------
14760
# +----------+ +--------+
@@ -175,7 +88,7 @@ def it_can_walk_phys_pkg_parts(self, _srels_for):
17588
phys_reader.blob_for.side_effect = [part_1_blob, part_2_blob, part_3_blob]
17689
# exercise ---------------------
17790
generated_tuples = [
178-
t for t in PackageReader._walk_phys_parts(phys_reader, pkg_srels)
91+
t for t in PackageReader(None)._walk_phys_parts(phys_reader, pkg_srels)
17992
]
18093
# verify -----------------------
18194
expected_tuples = [
@@ -195,36 +108,14 @@ def it_can_retrieve_srels_for_a_source_uri(
195108
load_from_xml = _SerializedRelationshipCollection_.load_from_xml
196109
srels = load_from_xml.return_value
197110
# exercise ---------------------
198-
retval = PackageReader._srels_for(phys_reader, source_uri)
111+
retval = PackageReader(None)._srels_for(phys_reader, source_uri)
199112
# verify -----------------------
200113
phys_reader.rels_xml_for.assert_called_once_with(source_uri)
201114
load_from_xml.assert_called_once_with(source_uri.baseURI, rels_xml)
202115
assert retval == srels
203116

204117
# fixture components -----------------------------------
205118

206-
@pytest.fixture
207-
def from_xml(self, request):
208-
return method_mock(request, _ContentTypeMap, "from_xml")
209-
210-
@pytest.fixture
211-
def init(self, request):
212-
return initializer_mock(request, PackageReader)
213-
214-
@pytest.fixture
215-
def _load_serialized_parts(self, request):
216-
return method_mock(request, PackageReader, "_load_serialized_parts")
217-
218-
@pytest.fixture
219-
def _PhysPkgReader_(self, request):
220-
_patch = patch("pptx.opc.serialized._PhysPkgReader", spec_set=_ZipPkgReader)
221-
request.addfinalizer(_patch.stop)
222-
return _patch.start()
223-
224-
@pytest.fixture
225-
def _SerializedPart_(self, request):
226-
return class_mock(request, "pptx.opc.serialized._SerializedPart")
227-
228119
@pytest.fixture
229120
def _SerializedRelationshipCollection_(self, request):
230121
return class_mock(
@@ -235,10 +126,6 @@ def _SerializedRelationshipCollection_(self, request):
235126
def _srels_for(self, request):
236127
return method_mock(request, PackageReader, "_srels_for")
237128

238-
@pytest.fixture
239-
def _walk_phys_parts(self, request):
240-
return method_mock(request, PackageReader, "_walk_phys_parts")
241-
242129

243130
class Describe_ContentTypeMap(object):
244131
def it_can_construct_from_ct_item_xml(self, from_xml_fixture):

0 commit comments

Comments
 (0)