Skip to content

Commit f46762c

Browse files
tiberlas=
authored andcommitted
rfctr: improve typing for Footnotes
1 parent 27b09fd commit f46762c

File tree

8 files changed

+101
-77
lines changed

8 files changed

+101
-77
lines changed

src/docx/document.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from docx.styles.style import ParagraphStyle, _TableStyle
2323
from docx.table import Table
2424
from docx.text.paragraph import Paragraph
25+
from docx.oxml.footnote import CT_Footnotes, CT_FtnEnd
26+
from docx.oxml.text.paragraph import CT_P
2527

2628

2729
class Document(ElementProxy):
@@ -113,7 +115,7 @@ def core_properties(self):
113115
return self._part.core_properties
114116

115117
@property
116-
def footnotes(self):
118+
def footnotes(self) -> CT_Footnotes:
117119
"""A |Footnotes| object providing access to footnote elements in this document."""
118120
return self._part.footnotes
119121

@@ -179,7 +181,7 @@ def tables(self) -> List[Table]:
179181
"""
180182
return self._body.tables
181183

182-
def _add_footnote(self, footnote_reference_ids):
184+
def _add_footnote(self, footnote_reference_ids: int) -> CT_FtnEnd:
183185
"""Inserts a newly created footnote to |Footnotes|."""
184186
return self._part.footnotes.add_footnote(footnote_reference_ids)
185187

@@ -196,7 +198,7 @@ def _body(self) -> _Body:
196198
self.__body = _Body(self._element.body, self)
197199
return self.__body
198200

199-
def _calculate_next_footnote_reference_id(self, p):
201+
def _calculate_next_footnote_reference_id(self, p: CT_P) -> int:
200202
"""
201203
Return the appropriate footnote reference id number for
202204
a new footnote added at the end of paragraph `p`.
@@ -228,7 +230,7 @@ def _calculate_next_footnote_reference_id(self, p):
228230
continue
229231
# These footnotes are after the new footnote, so we increment them.
230232
if not has_passed_containing_para:
231-
self.paragraphs[p_i].increment_containing_footnote_reference_ids()
233+
self.paragraphs[p_i]._increment_containing_footnote_reference_ids()
232234
else:
233235
# This is the last footnote before the new footnote, so we use its
234236
# value to determent the value of the new footnote.

src/docx/footnotes.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
from __future__ import annotations
44

5+
from typing import TYPE_CHECKING
6+
57
from docx.blkcntnr import BlockItemContainer
68
from docx.shared import Parented
79

10+
if TYPE_CHECKING:
11+
from docx import types as t
12+
from docx.oxml.footnote import CT_FtnEnd, CT_Footnotes
813

914
class Footnotes(Parented):
1015
"""
1116
Proxy object wrapping ``<w:footnotes>`` element.
1217
"""
13-
def __init__(self, footnotes, parent):
18+
def __init__(self, footnotes: CT_Footnotes, parent: t.ProvidesStoryPart):
1419
super(Footnotes, self).__init__(parent)
1520
self._element = self._footnotes = footnotes
1621

17-
def __getitem__(self, reference_id):
22+
def __getitem__(self, reference_id: int) -> Footnote:
1823
"""
1924
A |Footnote| for a specific footnote of reference id, defined with ``w:id`` argument of ``<w:footnoteReference>``.
2025
If reference id is invalid raises an |IndexError|
@@ -24,10 +29,10 @@ def __getitem__(self, reference_id):
2429
raise IndexError
2530
return Footnote(footnote, self)
2631

27-
def __len__(self):
32+
def __len__(self) -> int:
2833
return len(self._element)
2934

30-
def add_footnote(self, footnote_reference_id):
35+
def add_footnote(self, footnote_reference_id: int) -> Footnote:
3136
"""
3237
Return a newly created |Footnote|, the new footnote will
3338
be inserted in the correct spot by `footnote_reference_id`.
@@ -63,20 +68,20 @@ class Footnote(BlockItemContainer):
6368
"""
6469
Proxy object wrapping ``<w:footnote>`` element.
6570
"""
66-
def __init__(self, f, parent):
71+
def __init__(self, f: CT_FtnEnd, parent: t.ProvidesStoryPart):
6772
super(Footnote, self).__init__(f, parent)
6873
self._f = self._element = f
6974

70-
def __eq__(self, other):
75+
def __eq__(self, other) -> bool:
7176
if isinstance(other, Footnote):
7277
return self._f is other._f
7378
return False
7479

75-
def __ne__(self, other):
80+
def __ne__(self, other) -> bool:
7681
if isinstance(other, Footnote):
7782
return self._f is not other._f
7883
return True
7984

8085
@property
81-
def id(self):
86+
def id(self) -> int:
8287
return self._f.id

src/docx/oxml/footnote.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Custom element classes related to footnote (CT_FtnEnd, CT_Footnotes)."""
22

3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, Callable, List
6+
37
from docx.oxml.ns import qn
48
from docx.oxml.parser import OxmlElement
59
from docx.oxml.xmlchemy import (
@@ -9,25 +13,8 @@
913
ST_DecimalNumber
1014
)
1115

12-
class CT_Footnotes(BaseOxmlElement):
13-
"""
14-
``<w:footnotes>`` element, containing a sequence of footnote (w:footnote) elements
15-
"""
16-
footnote_sequence = OneOrMore('w:footnote')
17-
18-
def add_footnote(self, footnote_reference_id):
19-
"""
20-
Create a ``<w:footnote>`` element with `footnote_reference_id`.
21-
"""
22-
new_f = self.add_footnote_sequence()
23-
new_f.id = footnote_reference_id
24-
return new_f
25-
26-
def get_by_id(self, id):
27-
found = self.xpath(f'w:footnote[@w:id="{id}"]')
28-
if not found:
29-
return None
30-
return found[0]
16+
if TYPE_CHECKING:
17+
from docx.oxml.text.paragraph import CT_P
3118

3219

3320
class CT_FtnEnd(BaseOxmlElement):
@@ -37,7 +24,7 @@ class CT_FtnEnd(BaseOxmlElement):
3724
id = RequiredAttribute('w:id', ST_DecimalNumber)
3825
p = ZeroOrMore('w:p')
3926

40-
def add_footnote_before(self, footnote_reference_id):
27+
def add_footnote_before(self, footnote_reference_id: int) -> CT_FtnEnd:
4128
"""
4229
Create a ``<w:footnote>`` element with `footnote_reference_id`
4330
and insert it before the current element.
@@ -48,12 +35,34 @@ def add_footnote_before(self, footnote_reference_id):
4835
return new_footnote
4936

5037
@property
51-
def paragraphs(self):
52-
"""
53-
Returns a list of paragraphs |CT_P|, or |None| if none paragraph is present.
54-
"""
38+
def paragraphs(self) -> List[CT_P]:
39+
"""Returns a list of paragraphs |CT_P|."""
40+
5541
paragraphs = []
5642
for child in self:
5743
if child.tag == qn('w:p'):
5844
paragraphs.append(child)
5945
return paragraphs
46+
47+
48+
class CT_Footnotes(BaseOxmlElement):
49+
"""
50+
``<w:footnotes>`` element, containing a sequence of footnote (w:footnote) elements
51+
"""
52+
add_footnote_sequence: Callable[[], CT_FtnEnd]
53+
54+
footnote_sequence = OneOrMore('w:footnote')
55+
56+
def add_footnote(self, footnote_reference_id: int) -> CT_FtnEnd:
57+
"""
58+
Create a ``<w:footnote>`` element with `footnote_reference_id`.
59+
"""
60+
new_f = self.add_footnote_sequence()
61+
new_f.id = footnote_reference_id
62+
return new_f
63+
64+
def get_by_id(self, id: int) -> CT_FtnEnd | None:
65+
found = self.xpath(f'w:footnote[@w:id="{id}"]')
66+
if not found:
67+
return None
68+
return found[0]

src/docx/oxml/section.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
from docx.enum.section import WD_HEADER_FOOTER, WD_ORIENTATION, WD_SECTION_START
1414
from docx.oxml.ns import nsmap
15-
from docx.oxml.shared import CT_OnOff
16-
from docx.oxml.simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure, XsdString, ST_FtnPos, ST_NumberFormat, ST_RestartNumber
15+
from docx.oxml.shared import CT_OnOff, CT_DecimalNumber
16+
from docx.oxml.simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure, XsdString, ST_FtnPos, ST_NumberFormat, ST_RestartNumber, ST_DecimalNumber
1717
from docx.oxml.table import CT_Tbl
1818
from docx.oxml.text.paragraph import CT_P
1919
from docx.oxml.xmlchemy import (
@@ -35,13 +35,19 @@ class CT_FtnPos(BaseOxmlElement):
3535

3636
class CT_FtnProps(BaseOxmlElement):
3737
"""``<w:footnotePr>`` element, section wide footnote properties"""
38+
39+
get_or_add_pos: Callable[[], CT_FtnPos]
40+
get_or_add_numFmt: Callable[[], CT_NumFmt]
41+
get_or_add_numStart: Callable[[], CT_DecimalNumber]
42+
get_or_add_numRestart: Callable[[], CT_NumRestart]
43+
3844
_tag_seq = (
3945
'w:pos', 'w:numFmt', 'w:numStart', 'w:numRestart'
4046
)
41-
pos = ZeroOrOne('w:pos', successors=_tag_seq)
42-
numFmt = ZeroOrOne('w:numFmt', successors=_tag_seq[1:])
43-
numStart = ZeroOrOne('w:numStart', successors=_tag_seq[2:])
44-
numRestart = ZeroOrOne('w:numRestart', successors=_tag_seq[3:])
47+
pos: CT_FtnPos | None = ZeroOrOne('w:pos', successors=_tag_seq) # pyright: ignore[reportGeneralTypeIssues]
48+
numFmt: CT_NumFmt | None = ZeroOrOne('w:numFmt', successors=_tag_seq[1:]) # pyright: ignore[reportGeneralTypeIssues]
49+
numStart: CT_DecimalNumber | None = ZeroOrOne('w:numStart', successors=_tag_seq[2:]) # pyright: ignore[reportGeneralTypeIssues]
50+
numRestart: CT_NumRestart | None = ZeroOrOne('w:numRestart', successors=_tag_seq[3:]) # pyright: ignore[reportGeneralTypeIssues]
4551

4652

4753
class CT_HdrFtr(BaseOxmlElement):
@@ -132,6 +138,7 @@ class CT_SectPr(BaseOxmlElement):
132138
get_or_add_pgSz: Callable[[], CT_PageSz]
133139
get_or_add_titlePg: Callable[[], CT_OnOff]
134140
get_or_add_type: Callable[[], CT_SectType]
141+
get_or_add_footnotePr: Callable[[], CT_FtnProps]
135142
_add_footerReference: Callable[[], CT_HdrFtrRef]
136143
_add_headerReference: Callable[[], CT_HdrFtrRef]
137144
_remove_titlePg: Callable[[], None]
@@ -173,7 +180,9 @@ class CT_SectPr(BaseOxmlElement):
173180
titlePg: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
174181
"w:titlePg", successors=_tag_seq[14:]
175182
)
176-
footnotePr = ZeroOrOne("w:footnotePr", successors=_tag_seq[1:])
183+
footnotePr: CT_FtnProps | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
184+
"w:footnotePr", successors=_tag_seq[1:]
185+
)
177186
del _tag_seq
178187

179188
def add_footerReference(self, type_: WD_HEADER_FOOTER, rId: str) -> CT_HdrFtrRef:
@@ -241,7 +250,7 @@ def footer(self, value: int | Length | None):
241250
pgMar.footer = value if value is None or isinstance(value, Length) else Length(value)
242251

243252
@property
244-
def footnote_number_format(self):
253+
def footnote_number_format(self) -> ST_NumberFormat | None:
245254
"""
246255
The value of the ``w:val`` attribute in the ``<w:numFmt>`` child
247256
element of ``<w:footnotePr>`` element, as a |String|, or |None| if either the element or the
@@ -253,13 +262,13 @@ def footnote_number_format(self):
253262
return fPr.numFmt.val
254263

255264
@footnote_number_format.setter
256-
def footnote_number_format(self, value):
265+
def footnote_number_format(self, value: ST_NumberFormat | None):
257266
fPr = self.get_or_add_footnotePr()
258267
numFmt = fPr.get_or_add_numFmt()
259268
numFmt.val = value
260269

261270
@property
262-
def footnote_numbering_restart_location(self):
271+
def footnote_numbering_restart_location(self) -> ST_RestartNumber | None:
263272
"""
264273
The value of the ``w:val`` attribute in the ``<w:numRestart>`` child
265274
element of ``<w:footnotePr>`` element, as a |String|, or |None| if either the element or the
@@ -271,20 +280,20 @@ def footnote_numbering_restart_location(self):
271280
return fPr.numRestart.val
272281

273282
@footnote_numbering_restart_location.setter
274-
def footnote_numbering_restart_location(self, value):
283+
def footnote_numbering_restart_location(self, value: ST_RestartNumber | None):
275284
fPr = self.get_or_add_footnotePr()
276285
numStart = fPr.get_or_add_numStart()
277286
numRestart = fPr.get_or_add_numRestart()
278287
numRestart.val = value
279-
if numStart is None or len(numStart.values()) == 0:
288+
if len(numStart.values()) == 0:
280289
numStart.val = 1
281290
elif value != 'continuous':
282291
numStart.val = 1
283292
msg = "When ``<w:numRestart> is not 'continuous', then ``<w:numStart>`` must be 1."
284293
warn(msg, UserWarning, stacklevel=2)
285294

286295
@property
287-
def footnote_numbering_start_value(self):
296+
def footnote_numbering_start_value(self) -> ST_DecimalNumber | None:
288297
"""
289298
The value of the ``w:val`` attribute in the ``<w:numStart>`` child
290299
element of ``<w:footnotePr>`` element, as a |Number|, or |None| if either the element or the
@@ -296,20 +305,20 @@ def footnote_numbering_start_value(self):
296305
return fPr.numStart.val
297306

298307
@footnote_numbering_start_value.setter
299-
def footnote_numbering_start_value(self, value):
308+
def footnote_numbering_start_value(self, value: ST_DecimalNumber | None):
300309
fPr = self.get_or_add_footnotePr()
301310
numStart = fPr.get_or_add_numStart()
302311
numRestart = fPr.get_or_add_numRestart()
303312
numStart.val = value
304-
if numRestart is None or len(numRestart.values()) == 0:
313+
if len(numRestart.values()) == 0:
305314
numRestart.val = 'continuous'
306315
elif value != 1:
307316
numRestart.val = 'continuous'
308317
msg = "When ``<w:numStart> is not 1, then ``<w:numRestart>`` must be 'continuous'."
309318
warn(msg, UserWarning, stacklevel=2)
310319

311320
@property
312-
def footnote_position(self):
321+
def footnote_position(self) -> ST_FtnPos | None:
313322
"""
314323
The value of the ``w:val`` attribute in the ``<w:pos>`` child
315324
element of ``<w:footnotePr>`` element, as a |String|, or |None| if either the element or the
@@ -321,7 +330,7 @@ def footnote_position(self):
321330
return fPr.pos.val
322331

323332
@footnote_position.setter
324-
def footnote_position(self, value):
333+
def footnote_position(self, value: ST_FtnPos | None):
325334
fPr = self.get_or_add_footnotePr()
326335
pos = fPr.get_or_add_pos()
327336
pos.val = value

src/docx/oxml/text/paragraph.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,13 @@ def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]:
7171
)
7272

7373
@property
74-
def footnote_reference_ids(self):
75-
"""
76-
Return all footnote reference ids (``<w:footnoteReference>``) form the paragraph,
77-
or |None| if not present.
78-
"""
74+
def footnote_reference_ids(self) -> List[int]:
75+
"""Return all footnote reference ids (``<w:footnoteReference>``) form the paragraph."""
76+
7977
footnote_ids = []
8078
for run in self.r_lst:
8179
new_footnote_ids = run.footnote_reference_ids
82-
if new_footnote_ids:
80+
if new_footnote_ids and len(new_footnote_ids) > 0:
8381
footnote_ids.extend(new_footnote_ids)
8482
return footnote_ids
8583

src/docx/oxml/text/run.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from docx.oxml.shape import CT_Anchor, CT_Inline
1616
from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak
1717
from docx.oxml.text.parfmt import CT_TabStop
18+
from docx.oxml.text.footnote_reference import CT_FtnEdnRef
1819

1920
# ------------------------------------------------------------------------------------
2021
# Run-level elements
@@ -28,6 +29,9 @@ class CT_R(BaseOxmlElement):
2829
get_or_add_rPr: Callable[[], CT_RPr]
2930
_add_drawing: Callable[[], CT_Drawing]
3031
_add_t: Callable[..., CT_Text]
32+
_add_rPr: Callable[[], CT_RPr]
33+
_add_footnoteReference: Callable[[], CT_FtnEdnRef]
34+
footnoteReference_lst: List[CT_FtnEdnRef] | None
3135

3236
rPr: CT_RPr | None = ZeroOrOne("w:rPr") # pyright: ignore[reportAssignmentType]
3337
br = ZeroOrMore("w:br")
@@ -37,7 +41,7 @@ class CT_R(BaseOxmlElement):
3741
tab = ZeroOrMore("w:tab")
3842
footnoteReference = ZeroOrMore('w:footnoteReference')
3943

40-
def add_footnoteReference(self, id):
44+
def add_footnoteReference(self, id: int) -> CT_FtnEdnRef:
4145
"""
4246
Return a newly added ``<w:footnoteReference>`` element containing
4347
the footnote reference id.
@@ -105,7 +109,7 @@ def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]:
105109
return self.xpath("./w:lastRenderedPageBreak")
106110

107111
@property
108-
def footnote_reference_ids(self):
112+
def footnote_reference_ids(self) -> List[int] | None:
109113
"""
110114
Return all footnote reference ids (``<w:footnoteReference>``), or |None| if not present.
111115
"""
@@ -117,7 +121,7 @@ def footnote_reference_ids(self):
117121
references = None
118122
return references
119123

120-
def increment_containing_footnote_reference_ids(self):
124+
def increment_containing_footnote_reference_ids(self) -> CT_FtnEdnRef | None:
121125
"""
122126
Increment all footnote reference ids by one if they exist.
123127
Return all footnote reference ids (``<w:footnoteReference>``), or |None| if not present.

0 commit comments

Comments
 (0)