Skip to content

Commit c3ed33b

Browse files
committed
fix: scanny#754 _Relationships.items() raises
1 parent a5f05df commit c3ed33b

File tree

3 files changed

+51
-42
lines changed

3 files changed

+51
-42
lines changed

pptx/opc/package.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def iter_rels(self):
9696
visited = set()
9797

9898
def walk_rels(rels):
99-
for rel in rels:
99+
for rel in rels.values():
100100
yield rel
101101
# --- external items can have no relationships ---
102102
if rel.is_external:
@@ -482,14 +482,14 @@ def from_xml(cls, content_types_xml):
482482

483483

484484
class _Relationships(Mapping):
485-
"""Collection of |_Relationship| instances, largely having dict semantics.
485+
"""Collection of |_Relationship| instances having `dict` semantics.
486486
487487
Relationships are keyed by their rId, but may also be found in other ways, such as
488-
by their relationship type. `rels` is a dict of |Relationship| objects keyed by
489-
their rId.
488+
by their relationship type. |Relationship| objects are keyed by their rId.
490489
491-
Note that iterating this collection generates |Relationship| references (values),
492-
not rIds (keys) as it would for a dict.
490+
Iterating this collection has normal mapping semantics, generating the keys (rIds)
491+
of the mapping. `rels.keys()`, `rels.values()`, and `rels.items() can be used as
492+
they would be for a `dict`.
493493
"""
494494

495495
def __init__(self, base_uri):
@@ -507,9 +507,8 @@ def __getitem__(self, rId):
507507
raise KeyError("no relationship with key '%s'" % rId)
508508

509509
def __iter__(self):
510-
"""Implement iteration of relationships."""
511-
rels = self._rels
512-
return (rels[rId] for rId in sorted(rels.keys()))
510+
"""Implement iteration of rIds (iterating a mapping produces its keys)."""
511+
return iter(self._rels)
513512

514513
def __len__(self):
515514
"""Return count of relationships in collection."""
@@ -593,8 +592,22 @@ def xml(self):
593592
a `<?xml` header with encoding as UTF-8.
594593
"""
595594
rels_elm = CT_Relationships.new()
596-
for rel in self:
595+
596+
# -- Sequence <Relationship> elements deterministically (in numerical order) to
597+
# -- simplify testing and manual inspection.
598+
def iter_rels_in_numerical_order():
599+
sorted_num_rId_pairs = sorted(
600+
(
601+
int(rId[3:]) if rId.startswith("rId") and rId[3:].isdigit() else 0,
602+
rId,
603+
)
604+
for rId in self.keys()
605+
)
606+
return (self[rId] for _, rId in sorted_num_rId_pairs)
607+
608+
for rel in iter_rels_in_numerical_order():
597609
rels_elm.add_rel(rel.rId, rel.reltype, rel.target_ref, rel.is_external)
610+
598611
return rels_elm.xml
599612

600613
def _add_relationship(self, reltype, target, is_external=False):
@@ -648,7 +661,7 @@ def _rels(self):
648661
def _rels_by_reltype(self):
649662
"""defaultdict {reltype: [rels]} for all relationships in collection."""
650663
D = collections.defaultdict(list)
651-
for rel in self:
664+
for rel in self.values():
652665
D[rel.reltype].append(rel)
653666
return D
654667

tests/opc/test_package.py

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def it_can_iterate_over_its_relationships(self, request, _rels_prop_):
190190
part_0_, part_1_ = [
191191
instance_mock(request, Part, name="part_%d" % i) for i in range(2)
192192
]
193-
rels = tuple(
193+
all_rels = tuple(
194194
instance_mock(
195195
request,
196196
_Relationship,
@@ -208,18 +208,15 @@ def it_can_iterate_over_its_relationships(self, request, _rels_prop_):
208208
)
209209
)
210210
)
211-
_rels_prop_.return_value = rels[:3]
212-
part_0_.rels = rels[3:4]
213-
part_1_.rels = rels[4:]
211+
_rels_prop_.return_value = {r.rId: r for r in all_rels[:3]}
212+
part_0_.rels = {r.rId: r for r in all_rels[3:4]}
213+
part_1_.rels = {r.rId: r for r in all_rels[4:]}
214214
package = OpcPackage(None)
215215

216-
assert tuple(package.iter_rels()) == (
217-
rels[0],
218-
rels[3],
219-
rels[4],
220-
rels[1],
221-
rels[2],
222-
)
216+
rels = set(package.iter_rels())
217+
218+
# -- sequence is not guaranteed, but count (len) and uniqueness are --
219+
assert rels == set(all_rels)
223220

224221
def it_provides_access_to_the_main_document_part(self, request):
225222
presentation_part_ = instance_mock(request, PresentationPart)
@@ -234,8 +231,7 @@ def it_provides_access_to_the_main_document_part(self, request):
234231
assert presentation_part is presentation_part_
235232

236233
@pytest.mark.parametrize(
237-
"ns, expected_n",
238-
(((), 1), ((1,), 2), ((1, 2), 3), ((2, 4), 3), ((1, 4), 3)),
234+
"ns, expected_n", (((), 1), ((1,), 2), ((1, 2), 3), ((2, 4), 3), ((1, 4), 3))
239235
)
240236
def it_can_find_the_next_available_partname(self, request, ns, expected_n):
241237
tmpl = "/x%d.xml"
@@ -659,13 +655,15 @@ def but_it_raises_KeyError_when_no_relationship_has_rId(self, _rels_prop_):
659655
_Relationships(None)["rId6"]
660656
assert str(e.value) == "\"no relationship with key 'rId6'\""
661657

662-
def it_can_iterate_the_relationships_it_contains(self, request, _rels_prop_):
658+
def it_can_iterate_the_rIds_of_the_relationships_it_contains(
659+
self, request, _rels_prop_
660+
):
663661
rels_ = set(instance_mock(request, _Relationship) for n in range(5))
664662
_rels_prop_.return_value = {"rId%d" % (i + 1): r for i, r in enumerate(rels_)}
665663
relationships = _Relationships(None)
666664

667-
for r in relationships:
668-
rels_.remove(r)
665+
for rId in relationships:
666+
rels_.remove(relationships[rId])
669667

670668
assert len(rels_) == 0
671669

@@ -795,10 +793,10 @@ def it_can_pop_a_relationship_to_remove_it_from_the_collection(
795793

796794
def it_can_serialize_itself_to_XML(self, request, _rels_prop_):
797795
_rels_prop_.return_value = {
798-
"rId1": instance_mock(
796+
"rId11": instance_mock(
799797
request,
800798
_Relationship,
801-
rId="rId1",
799+
rId="rId11",
802800
reltype=RT.SLIDE,
803801
target_ref="../slides/slide1.xml",
804802
is_external=False,
@@ -811,6 +809,14 @@ def it_can_serialize_itself_to_XML(self, request, _rels_prop_):
811809
target_ref="http://url",
812810
is_external=True,
813811
),
812+
"foo7W": instance_mock(
813+
request,
814+
_Relationship,
815+
rId="foo7W",
816+
reltype=RT.IMAGE,
817+
target_ref="../media/image1.png",
818+
is_external=False,
819+
),
814820
}
815821
relationships = _Relationships(None)
816822

@@ -867,12 +873,7 @@ def and_it_can_add_an_external_relationship_to_help(
867873
),
868874
)
869875
def it_can_get_a_matching_relationship_to_help(
870-
self,
871-
request,
872-
_rels_by_reltype_prop_,
873-
target_ref,
874-
is_external,
875-
expected_value,
876+
self, request, _rels_by_reltype_prop_, target_ref, is_external, expected_value
876877
):
877878
part_1, part_2 = (instance_mock(request, Part) for _ in range(2))
878879
_rels_by_reltype_prop_.return_value = {
@@ -996,12 +997,7 @@ def it_can_construct_from_xml(self, request, part_):
996997
relationship = _Relationship.from_xml("/ppt", rel_elm, parts)
997998

998999
_init_.assert_called_once_with(
999-
relationship,
1000-
"/ppt",
1001-
"rId42",
1002-
RT.SLIDE,
1003-
RTM.INTERNAL,
1004-
part_,
1000+
relationship, "/ppt", "rId42", RT.SLIDE, RTM.INTERNAL, part_
10051001
)
10061002
assert isinstance(relationship, _Relationship)
10071003

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
2-
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="../slides/slide1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://url" TargetMode="External"/></Relationships>
2+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="foo7W" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="http://url" TargetMode="External"/><Relationship Id="rId11" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="../slides/slide1.xml"/></Relationships>

0 commit comments

Comments
 (0)