Skip to content

Commit 6c34f12

Browse files
committed
rfctr: modernize opc.shared.lazyproperty
No need for two, use the already modernized `docx.shared.lazyproperty`.
1 parent d8a3289 commit 6c34f12

File tree

8 files changed

+277
-308
lines changed

8 files changed

+277
-308
lines changed

src/docx/opc/package.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
"""Objects that implement reading and writing OPC packages."""
22

3+
from __future__ import annotations
4+
5+
from typing import IO, TYPE_CHECKING, Iterator
6+
37
from docx.opc.constants import RELATIONSHIP_TYPE as RT
48
from docx.opc.packuri import PACKAGE_URI, PackURI
59
from docx.opc.part import PartFactory
610
from docx.opc.parts.coreprops import CorePropertiesPart
711
from docx.opc.pkgreader import PackageReader
812
from docx.opc.pkgwriter import PackageWriter
913
from docx.opc.rel import Relationships
10-
from docx.opc.shared import lazyproperty
14+
from docx.shared import lazyproperty
15+
16+
if TYPE_CHECKING:
17+
from docx.opc.part import Part
1118

1219

1320
class OpcPackage:
@@ -56,7 +63,7 @@ def walk_rels(source, visited=None):
5663
for rel in walk_rels(self):
5764
yield rel
5865

59-
def iter_parts(self):
66+
def iter_parts(self) -> Iterator[Part]:
6067
"""Generate exactly one reference to each of the parts in the package by
6168
performing a depth-first traversal of the rels graph."""
6269

@@ -76,7 +83,7 @@ def walk_parts(source, visited=[]):
7683
for part in walk_parts(self):
7784
yield part
7885

79-
def load_rel(self, reltype, target, rId, is_external=False):
86+
def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False):
8087
"""Return newly added |_Relationship| instance of `reltype` between this part
8188
and `target` with key `rId`.
8289
@@ -111,14 +118,14 @@ def next_partname(self, template):
111118
return PackURI(candidate_partname)
112119

113120
@classmethod
114-
def open(cls, pkg_file):
121+
def open(cls, pkg_file: str | IO[bytes]) -> OpcPackage:
115122
"""Return an |OpcPackage| instance loaded with the contents of `pkg_file`."""
116123
pkg_reader = PackageReader.from_file(pkg_file)
117124
package = cls()
118125
Unmarshaller.unmarshal(pkg_reader, package, PartFactory)
119126
return package
120127

121-
def part_related_by(self, reltype):
128+
def part_related_by(self, reltype: str) -> Part:
122129
"""Return part to which this package has a relationship of `reltype`.
123130
124131
Raises |KeyError| if no such relationship is found and |ValueError| if more than
@@ -127,13 +134,16 @@ def part_related_by(self, reltype):
127134
return self.rels.part_with_reltype(reltype)
128135

129136
@property
130-
def parts(self):
137+
def parts(self) -> list[Part]:
131138
"""Return a list containing a reference to each of the parts in this package."""
132139
return list(self.iter_parts())
133140

134-
def relate_to(self, part, reltype):
135-
"""Return rId key of relationship to `part`, from the existing relationship if
136-
there is one, otherwise a newly created one."""
141+
def relate_to(self, part: Part, reltype: str):
142+
"""Return rId key of new or existing relationship to `part`.
143+
144+
If a relationship of `reltype` to `part` already exists, its rId is returned. Otherwise a
145+
new relationship is created and that rId is returned.
146+
"""
137147
rel = self.rels.get_or_add(reltype, part)
138148
return rel.rId
139149

@@ -143,9 +153,11 @@ def rels(self):
143153
relationships for this package."""
144154
return Relationships(PACKAGE_URI.baseURI)
145155

146-
def save(self, pkg_file):
147-
"""Save this package to `pkg_file`, where `file` can be either a path to a file
148-
(a string) or a file-like object."""
156+
def save(self, pkg_file: str | IO[bytes]):
157+
"""Save this package to `pkg_file`.
158+
159+
`pkg_file` can be either a file-path or a file-like object.
160+
"""
149161
for part in self.parts:
150162
part.before_marshal()
151163
PackageWriter.write(pkg_file, self.rels, self.parts)
@@ -190,9 +202,7 @@ def _unmarshal_parts(pkg_reader, package, part_factory):
190202
"""
191203
parts = {}
192204
for partname, content_type, reltype, blob in pkg_reader.iter_sparts():
193-
parts[partname] = part_factory(
194-
partname, content_type, reltype, blob, package
195-
)
205+
parts[partname] = part_factory(partname, content_type, reltype, blob, package)
196206
return parts
197207

198208
@staticmethod
@@ -202,7 +212,5 @@ def _unmarshal_relationships(pkg_reader, package, parts):
202212
in `parts`."""
203213
for source_uri, srel in pkg_reader.iter_srels():
204214
source = package if source_uri == "/" else parts[source_uri]
205-
target = (
206-
srel.target_ref if srel.is_external else parts[srel.target_partname]
207-
)
215+
target = srel.target_ref if srel.is_external else parts[srel.target_partname]
208216
source.load_rel(srel.reltype, target, srel.rId, srel.is_external)

src/docx/opc/part.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from docx.opc.oxml import serialize_part_xml
88
from docx.opc.packuri import PackURI
99
from docx.opc.rel import Relationships
10-
from docx.opc.shared import cls_method_fn, lazyproperty
10+
from docx.opc.shared import cls_method_fn
1111
from docx.oxml.parser import parse_xml
12+
from docx.shared import lazyproperty
1213

1314
if TYPE_CHECKING:
1415
from docx.package import Package
@@ -81,9 +82,10 @@ def drop_rel(self, rId: str):
8182
def load(cls, partname: str, content_type: str, blob: bytes, package: Package):
8283
return cls(partname, content_type, blob, package)
8384

84-
def load_rel(self, reltype, target, rId, is_external=False):
85-
"""Return newly added |_Relationship| instance of `reltype` between this part
86-
and `target` with key `rId`.
85+
def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False):
86+
"""Return newly added |_Relationship| instance of `reltype`.
87+
88+
The new relationship relates the `target` part to this part with key `rId`.
8789
8890
Target mode is set to ``RTM.EXTERNAL`` if `is_external` is |True|. Intended for
8991
use during load from a serialized package, where the rId is well-known. Other
@@ -118,7 +120,7 @@ def part_related_by(self, reltype: str) -> Part:
118120
"""
119121
return self.rels.part_with_reltype(reltype)
120122

121-
def relate_to(self, target: Part, reltype: str, is_external: bool = False) -> str:
123+
def relate_to(self, target: Part | str, reltype: str, is_external: bool = False) -> str:
122124
"""Return rId key of relationship of `reltype` to `target`.
123125
124126
The returned `rId` is from an existing relationship if there is one, otherwise a
@@ -142,7 +144,7 @@ def rels(self):
142144
"""|Relationships| instance holding the relationships for this part."""
143145
return Relationships(self._partname.baseURI)
144146

145-
def target_ref(self, rId):
147+
def target_ref(self, rId: str) -> str:
146148
"""Return URL contained in target ref of relationship identified by `rId`."""
147149
rel = self.rels[rId]
148150
return rel.target_ref

src/docx/opc/pkgwriter.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@
44
OpcPackage.save().
55
"""
66

7+
from __future__ import annotations
8+
9+
from typing import TYPE_CHECKING, Iterable
10+
711
from docx.opc.constants import CONTENT_TYPE as CT
812
from docx.opc.oxml import CT_Types, serialize_part_xml
913
from docx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI
1014
from docx.opc.phys_pkg import PhysPkgWriter
1115
from docx.opc.shared import CaseInsensitiveDict
1216
from docx.opc.spec import default_content_types
1317

18+
if TYPE_CHECKING:
19+
from docx.opc.part import Part
20+
1421

1522
class PackageWriter:
1623
"""Writes a zip-format OPC package to `pkg_file`, where `pkg_file` can be either a
@@ -38,13 +45,13 @@ def _write_content_types_stream(phys_writer, parts):
3845
phys_writer.write(CONTENT_TYPES_URI, cti.blob)
3946

4047
@staticmethod
41-
def _write_parts(phys_writer, parts):
48+
def _write_parts(phys_writer: PhysPkgWriter, parts: Iterable[Part]):
4249
"""Write the blob of each part in `parts` to the package, along with a rels item
4350
for its relationships if and only if it has any."""
4451
for part in parts:
4552
phys_writer.write(part.partname, part.blob)
46-
if len(part._rels):
47-
phys_writer.write(part.partname.rels_uri, part._rels.xml)
53+
if len(part.rels):
54+
phys_writer.write(part.partname.rels_uri, part.rels.xml)
4855

4956
@staticmethod
5057
def _write_pkg_rels(phys_writer, pkg_rels):

src/docx/opc/rel.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
from __future__ import annotations
44

5-
from typing import Any, Dict
5+
from typing import TYPE_CHECKING, Any, Dict
66

77
from docx.opc.oxml import CT_Relationships
88

9+
if TYPE_CHECKING:
10+
from docx.opc.part import Part
11+
912

1013
class Relationships(Dict[str, "_Relationship"]):
1114
"""Collection object for |_Relationship| instances, having list semantics."""
@@ -16,7 +19,7 @@ def __init__(self, baseURI: str):
1619
self._target_parts_by_rId: Dict[str, Any] = {}
1720

1821
def add_relationship(
19-
self, reltype: str, target: str | Any, rId: str, is_external: bool = False
22+
self, reltype: str, target: Part | str, rId: str, is_external: bool = False
2023
) -> "_Relationship":
2124
"""Return a newly added |_Relationship| instance."""
2225
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
@@ -25,7 +28,7 @@ def add_relationship(
2528
self._target_parts_by_rId[rId] = target
2629
return rel
2730

28-
def get_or_add(self, reltype, target_part):
31+
def get_or_add(self, reltype: str, target_part: Part) -> _Relationship:
2932
"""Return relationship of `reltype` to `target_part`, newly added if not already
3033
present in collection."""
3134
rel = self._get_matching(reltype, target_part)
@@ -64,7 +67,9 @@ def xml(self):
6467
rels_elm.add_rel(rel.rId, rel.reltype, rel.target_ref, rel.is_external)
6568
return rels_elm.xml
6669

67-
def _get_matching(self, reltype, target, is_external=False):
70+
def _get_matching(
71+
self, reltype: str, target: Part | str, is_external: bool = False
72+
) -> _Relationship | None:
6873
"""Return relationship of matching `reltype`, `target`, and `is_external` from
6974
collection, or None if not found."""
7075

@@ -99,7 +104,7 @@ def _get_rel_of_type(self, reltype):
99104
return matching[0]
100105

101106
@property
102-
def _next_rId(self):
107+
def _next_rId(self) -> str:
103108
"""Next available rId in collection, starting from 'rId1' and making use of any
104109
gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3']."""
105110
for n in range(1, len(self) + 2):
@@ -135,8 +140,7 @@ def rId(self):
135140
def target_part(self):
136141
if self._is_external:
137142
raise ValueError(
138-
"target_part property on _Relationship is undef"
139-
"ined when target mode is External"
143+
"target_part property on _Relationship is undef" "ined when target mode is External"
140144
)
141145
return self._target
142146

src/docx/opc/shared.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
"""Objects shared by opc modules."""
22

3+
from __future__ import annotations
34

4-
class CaseInsensitiveDict(dict):
5+
from typing import Any, Dict, TypeVar
6+
7+
_T = TypeVar("_T")
8+
9+
10+
class CaseInsensitiveDict(Dict[str, Any]):
511
"""Mapping type that behaves like dict except that it matches without respect to the
612
case of the key.
713
@@ -23,23 +29,3 @@ def __setitem__(self, key, value):
2329
def cls_method_fn(cls: type, method_name: str):
2430
"""Return method of `cls` having `method_name`."""
2531
return getattr(cls, method_name)
26-
27-
28-
def lazyproperty(f):
29-
"""@lazyprop decorator.
30-
31-
Decorated method will be called only on first access to calculate a cached property
32-
value. After that, the cached value is returned.
33-
"""
34-
cache_attr_name = "_%s" % f.__name__ # like '_foobar' for prop 'foobar'
35-
docstring = f.__doc__
36-
37-
def get_prop_value(obj):
38-
try:
39-
return getattr(obj, cache_attr_name)
40-
except AttributeError:
41-
value = f(obj)
42-
setattr(obj, cache_attr_name, value)
43-
return value
44-
45-
return property(get_prop_value, doc=docstring)

0 commit comments

Comments
 (0)