8000 rfctr: add types to Run and its tests · python-openxml/python-docx@45bf74b · GitHub
[go: up one dir, main page]

Skip to content

Commit 45bf74b

Browse files
committed
rfctr: add types to Run and its tests
1 parent fd54be1 commit 45bf74b

File tree

6 files changed

+145
-61
lines changed

6 files changed

+145
-61
lines changed

src/docx/oxml/text/run.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from typing import Callable
6+
57
from docx.oxml.ns import qn
68
from docx.oxml.simpletypes import ST_BrClear, ST_BrType
79
from docx.oxml.text.font import CT_RPr
@@ -14,12 +16,16 @@
1416
class CT_R(BaseOxmlElement):
1517
"""`<w:r>` element, containing the properties and text for a run."""
1618

19+
add_br: Callable[[], CT_Br]
20+
get_or_add_rPr: Callable[[], CT_RPr]
21+
_add_t: Callable[..., CT_Text]
22+
1723
rPr = ZeroOrOne("w:rPr")
18-
t = ZeroOrMore("w:t")
1924
br = ZeroOrMore("w:br")
2025
cr = ZeroOrMore("w:cr")
21-
tab = ZeroOrMore("w:tab")
2226
drawing = ZeroOrMore(< D7AE span class=pl-s>"w:drawing")
27+
t = ZeroOrMore("w:t")
28+
tab = ZeroOrMore("w:tab")
2329

2430
def add_t(self, text: str) -> CT_Text:
2531
"""Return a newly added `<w:t>` element containing `text`."""
@@ -44,7 +50,7 @@ def clear_content(self):
4450
self.remove(child)
4551

4652
@property
47-
def style(self):
53+
def style(self) -> str | None:
4854
"""String contained in `w:val` attribute of `w:rStyle` grandchild.
4955
5056
|None| if that element is not present.
@@ -64,7 +70,7 @@ def style(self, style):
6470
rPr.style = style
6571

6672
@property
67-
def text(self):
73+
def text(self) -> str:
6874
"""The textual content of this run.
6975
7076
Inner-content child elements like `w:tab` are translated to their text
@@ -82,7 +88,7 @@ def text(self):
8288
return text
8389

8490
@text.setter
85-
def text(self, text):
91+
def text(self, text: str):
8692
self.clear_content()
8793
_RunContentAppender.append_to_run_from_text(self, text)
8894

@@ -98,7 +104,7 @@ def _insert_rPr(self, rPr: CT_RPr) -> CT_RPr:
98104
class CT_Br(BaseOxmlElement):
99105
"""`<w:br>` element, indicating a line, page, or column break in a run."""
100106

101-
type = OptionalAttribute("w:type", ST_BrType)
107+
type = OptionalAttribute("w:type", ST_BrType, default="textWrapping")
102108
clear = OptionalAttribute("w:clear", ST_BrClear)
103109

104110

@@ -119,23 +125,23 @@ class _RunContentAppender(object):
119125
appended.
120126
"""
121127

122-
def __init__(self, r):
128+
def __init__(self, r: CT_R):
123129
self._r = r
124130
self._bfr = []
125131

126132
@classmethod
127-
def append_to_run_from_text(cls, r, text):
133+
def append_to_run_from_text(cls, r: CT_R, text: str):
128134
"""Append inner-content elements for `text` to `r` element."""
129135
appender = cls(r)
130136
appender.add_text(text)
131137

132-
def add_text(self, text):
138+
def add_text(self, text: str):
133139
"""Append inner-content elements for `text` to the `w:r` element."""
134140
for char in text:
135141
self.add_char(char)
136142
self.flush()
137143

138-
def add_char(self, char):
144+
def add_char(self, char: str):
139145
"""Process next character of input through finite state maching (FSM).
140146
141147
There are two possible states, buffer pending and not pending, but those are

src/docx/text/run.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
11
"""Run-related proxy objects for python-docx, Run in particular."""
22

3+
from __future__ import annotations
4+
5+
from typing import IO
6+
7+
from docx import types as t
38
from docx.enum.style import WD_STYLE_TYPE
49
from docx.enum.text import WD_BREAK
10+
from docx.oxml.text.run import CT_R, CT_Text
511
from docx.shape import InlineShape
6-
from docx.shared import Parented
12+
from docx.shared import Length, Parented
13+
from docx.styles.style import CharacterStyle
714
from docx.text.font import Font
815

916

1017
class Run(Parented):
11-
"""Proxy object wrapping ``<w:r>`` element.
18+
"""Proxy object wrapping `<w:r>` element.
1219
1320
Several of the properties on Run take a tri-state value, |True|, |False|, or |None|.
1421
|True| and |False| correspond to on and off respectively. |None| indicates the
1522
property is not specified directly on the run and its effective value is taken from
1623
the style hierarchy.
1724
"""
1825

19-
def __init__(self, r, parent):
26+
def __init__(self, r: CT_R, parent: t.StoryChild):
2027
super(Run, self).__init__(parent)
2128
self._r = self._element = self.element = r
2229

23-
def add_break(self, break_type: WD_BREAK = WD_BREAK.LINE):
30+
def add_break(self, break_type: WD_BREAK = WD_BREAK.LINE): # pyright: ignore
2431
"""Add a break element of `break_type` to this run.
2532
2633
`break_type` can take the values `WD_BREAK.LINE`, `WD_BREAK.PAGE`, and
@@ -41,7 +48,12 @@ def add_break(self, break_type: WD_BREAK = WD_BREAK.LINE):
4148
if clear is not None:
4249
br.clear = clear
4350

44-
def add_picture(self, image_path_or_stream, width=None, height=None):
51+
def add_picture(
52+
self,
53+
image_path_or_stream: str | IO[bytes],
54+
width: Length | None = None,
55+
height: Length | None = None,
56+
) -> InlineShape:
4557
"""Return an |InlineShape| instance containing the image identified by
4658
`image_path_or_stream`, added to the end of this run.
4759
@@ -62,7 +74,7 @@ def add_tab(self):
6274
tab character."""
6375
self._r._add_tab()
6476

65-
def add_text(self, text):
77+
def add_text(self, text: str):
6678
"""Returns a newly appended |_Text| object (corresponding to a new ``<w:t>``
6779
child element) to the run, containing `text`.
6880
@@ -73,15 +85,15 @@ def add_text(self, text):
7385
return _Text(t)
7486

7587
@property
76-
def bold(self):
88+
def bold(self) -> bool:
7789
"""Read/write.
7890
7991
Causes the text of the run to appear in bold.
8092
"""
8193
return self.font.bold
8294

8395
@bold.setter
84-
def bold(self, value):
96+
def bold(self, value: bool):
8597
self.font.bold = value
8698

8799
def clear(self):
@@ -99,19 +111,19 @@ def font(self):
99111
return Font(self._element)
100112

101113
@property
102-
def italic(self):
114+
def italic(self) -> bool:
103115
"""Read/write tri-state value.
104116
105117
When |True|, causes the text of the run to appear in italics.
106118
"""
107119
return self.font.italic
108120

109121
@italic.setter
110-
def italic(self, value):
122+
def italic(self, value: bool):
111123
self.font.italic = value
112124

113125
@property
114-
def style(self):
126+
def style(self) -> CharacterStyle | None:
115127
"""Read/write.
116128
117129
A |_CharacterStyle| object representing the character style applied to this run.
@@ -123,7 +135,7 @@ def style(self):
123135
return self.part.get_style(style_id, WD_STYLE_TYPE.CHARACTER)
124136

125137
@style.setter
126-
def style(self, style_or_name):
138+
def style(self, style_or_name: str | CharacterStyle | None):
127139
style_id = self.part.get_style_id(style_or_name, WD_STYLE_TYPE.CHARACTER)
128140
self._r.style = style_id
129141

@@ -146,11 +158,11 @@ def text(self) -> str:
146158
return self._r.text
147159

148160
@text.setter
149-
def text(self, text):
161+
def text(self, text: str):
150162
self._r.text = text
151163

152164
@property
153-
def underline(self):
165+
def underline(self) -> bool:
154166
"""The underline style for this |Run|, one of |None|, |True|, |False|, or a
155167
value from :ref:`WdUnderline`.
156168
@@ -165,13 +177,13 @@ def underline(self):
165177
return self.font.underline
166178

167179
@underline.setter
168-
def underline(self, value):
180+
def underline(self, value: bool):
169181
self.font.underline = value
170182

171183

172184
class _Text(object):
173185
"""Proxy object wrapping `<w:t>` element."""
174186

175-
def __init__(self, t_elm):
187+
def __init__(self, t_elm: CT_Text):
176188
super(_Text, self).__init__()
177189
self._t = t_elm

src/docx/types.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Abstract types used by `python-docx`."""
2+
3+
from __future__ import annotations
4+
5+
from typing_extensions import Protocol
6+
7+
from docx.parts.story import StoryPart
8+
9+
10+
class StoryChild(Protocol):
11+
"""An object that can fulfill the `parent` role in a `Parented` class.
12+
13+
This type is for objects that have a story part like document or header as their
14+
root part.
15+
"""
16+
17+
@property
18+
def part(self) -> StoryPart:
19+
...

tests/text/test_run.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,14 @@ def it_can_add_text(self, add_text_fixture, Text_):
7575
(WD_BREAK.LINE, "w:r/w:br"),
7676
(WD_BREAK.PAGE, "w:r/w:br{w:type=page}"),
7777
(WD_BREAK.COLUMN, "w:r/w:br{w:type=column}"),
78-
(WD_BREAK.LINE_CLEAR_LEFT, "w:r/w:br{w:type=textWrapping, w:clear=left}"),
79-
(WD_BREAK.LINE_CLEAR_RIGHT, "w:r/w:br{w:type=textWrapping, w:clear=right}"),
80-
(WD_BREAK.LINE_CLEAR_ALL, "w:r/w:br{w:type=textWrapping, w:clear=all}"),
78+
(WD_BREAK.LINE_CLEAR_LEFT, "w:r/w:br{w:clear=left}"),
79+
(WD_BREAK.LINE_CLEAR_RIGHT, "w:r/w:br{w:clear=right}"),
80+
(WD_BREAK.LINE_CLEAR_ALL, "w:r/w:br{w:clear=all}"),
8181
],
8282
)
8383
def it_can_add_a_break(self, break_type: WD_BREAK, expected_cxml: str):
84-
run = Run(element("w:r"), None)
84+
r = cast(CT_R, element("w:r"))
85+
run = Run(r, None) # pyright:ignore[reportGeneralTypeIssues]
8586
expected_xml = xml(expected_cxml)
8687

8788
run.add_break(break_type)

tests/unitutil/file.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
"""Utility functions for loading files for unit testing."""
22

3+
from __future__ import annotations
4+
35
import os
46

57
_thisdir = os.path.split(__file__)[0]
68
test_file_dir = os.path.abspath(os.path.join(_thisdir, "..", "test_files"))
79

810

9-
def abspath(relpath):
11+
def abspath(relpath: str) -> str:
1012
thisdir = os.path.split(__file__)[0]
1113
return os.path.abspath(os.path.join(thisdir, relpath))
1214

1315

14-
def absjoin(*paths):
16+
def absjoin(*paths: str) -> str:
1517
return os.path.abspath(os.path.join(*paths))
1618

1719

18-
def docx_path(name):
20+
def docx_path(name: str):
1921
"""
2022
Return the absolute path to test .docx file with root name `name`.
2123
"""
2224
return absjoin(test_file_dir, "%s.docx" % name)
2325

2426

25-
def snippet_seq(name, offset=0, count=1024):
27+
def snippet_seq(name: str, offset: int = 0, count: int = 1024):
2628
"""
2729
Return a tuple containing the unicode text snippets read from the snippet
2830
file having `name`. Snippets are delimited by a blank line. If specified,
@@ -36,7 +38,7 @@ def snippet_seq(name, offset=0, count=1024):
3638
return tuple(snippets[start:end])
3739

3840

39-
def snippet_text(snippet_file_name):
41+
def snippet_text(snippet_file_name: str):
4042
"""
4143
Return the unicode text read from the test snippet file having
4244
`snippet_file_name`.
@@ -49,7 +51,7 @@ def snippet_text(snippet_file_name):
4951
return snippet_bytes.decode("utf-8")
5052

5153

52-
def test_file(name):
54+
def test_file(name: str):
5355
"""
5456
Return the absolute path to test file having `name`.
5557
"""

0 commit comments

Comments
 (0)
0