8000 rfctr: improve typing · python-openxml/python-docx@08ab7e6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 08ab7e6

Browse files
committed
rfctr: improve typing
1 parent 99e9a0e commit 08ab7e6

File tree

15 files changed

+149
-81
lines changed

15 files changed

+149
-81
lines changed

pyrightconfig.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"exclude": [
3+
"**/__pycache__",
4+
"**/.*"
5+
],
6+
"ignore": [
7+
],
8+
"include": [
9+
"src/docx/",
10+
"tests"
11+
],
12+
"pythonPlatform": "All",
13+
"pythonVersion": "3.7",
14+
"reportImportCycles": true,
15+
"reportUnnecessaryCast": true,
16+
"typeCheckingMode": "strict",
17+
"useLibraryCodeForTypes": false,
18+
"verboseOutput": true
19+
}

requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ behave>=1.2.3
33
pyparsing>=2.0.1
44
pytest>=2.5
55
ruff
6+
typing-extensions

src/docx/api.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
Provides a syntactically more convenient API for interacting with the OpcPackage graph.
44
"""
55

6+
from __future__ import annotations
7+
68
import os
9+
from typing import IO
710

811
from docx.opc.constants import CONTENT_TYPE as CT
912
from docx.package import Package
1013

1114

12-
def Document(docx=None):
15+
def Document(docx: str | IO[bytes] | None = None):
1316
"""Return a |Document| object loaded from `docx`, where `docx` can be either a path
1417
to a ``.docx`` file (a string) or a file-like object.
1518

src/docx/opc/oxml.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
# ===========================================================================
2828

2929

30-
def parse_xml(text):
31-
"""``etree.fromstring()`` replacement that uses oxml parser."""
30+
def parse_xml(text: str) -> etree._Element: # pyright: ignore[reportPrivateUsage]
31+
"""`etree.fromstring()` replacement that uses oxml parser."""
3232
return etree.fromstring(text, oxml_parser)
3333

3434

src/docx/opc/rel.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
"""Relationship-related objects."""
22

3-
from .oxml import CT_Relationships
3+
from __future__ import annotations
44

5+
from typing import Any, Dict
56

6-
class Relationships(dict):
7+
from docx.opc.oxml import CT_Relationships
8+
9+
10+
class Relationships(Dict[str, "_Relationship"]):
711
"""Collection object for |_Relationship| instances, having list semantics."""
812

9-
def __init__(self, baseURI):
13+
def __init__(self, baseURI: str):
1014
super(Relationships, self).__init__()
1115
self._baseURI = baseURI
12-
self._target_parts_by_rId = {}
16+
self._target_parts_by_rId: Dict[str, Any] = {}
1317

14-
def add_relationship(self, reltype, target, rId, is_external=False):
18+
def add_relationship(
19+
self, reltype: str, target: str | Any, rId: str, is_external: bool = False
20+
) -> "_Relationship":
1521
"""Return a newly added |_Relationship| instance."""
1622
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
1723
self[rId] = rel
@@ -105,7 +111,7 @@ def _next_rId(self):
105111
class _Relationship(object):
106112
"""Value object for relationship to part."""
107113

108-
def __init__(self, rId, reltype, target, baseURI, external=False):
114+
def __init__(self, rId: str, reltype, target, baseURI, external=False):
109115
super(_Relationship, self).__init__()
110116
self._rId = rId
111117
self._reltype = reltype
@@ -135,7 +141,7 @@ def target_part(self):
135141
return self._target
136142

137143
@property
138-
def target_ref(self):
144+
def target_ref(self) -> str:
139145
if self._is_external:
140146
return self._target
141147
else:

src/docx/oxml/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This including registering custom element classes corresponding to Open XML elements.
44
"""
55

6+
from __future__ import annotations
7+
68
from lxml import etree
79

810
from docx.oxml.ns import NamespacePrefixedTag, nsmap

src/docx/oxml/coreprops.py

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

33
import re
44
from datetime import datetime, timedelta
5+
from typing import Any
56

67
from docx.oxml import parse_xml
78
from docx.oxml.ns import nsdecls, qn
@@ -47,31 +48,31 @@ def author_text(self):
4748
return self._text_of_element("creator")
4849

4950
@author_text.setter
50-
def author_text(self, value):
51+
def author_text(self, value: str):
5152
self._set_element_text("creator", value)
5253

5354
@property
54-
def category_text(self):
55+
def category_text(self) -> str:
5556
return self._text_of_element("category")
5657

5758
@category_text.setter
58-
def category_text(self, value):
59+
def category_text(self, value: str):
5960
self._set_element_text("category", value)
6061

6162
@property
62-
def comments_text(self):
63+
def comments_text(self) -> str:
6364
return self._text_of_element("description")
6465

6566
@comments_text.setter
66-
def comments_text(self, value):
67+
def comments_text(self, value: str):
6768
self._set_element_text("description", value)
6869

6970
@property
7071
def contentStatus_text(self):
7172
return self._text_of_element("contentStatus")
7273

7374
@contentStatus_text.setter
74-
def contentStatus_text(self, value):
75+
def contentStatus_text(self, value: str):
7576
self._set_element_text("contentStatus", value)
7677

7778
@property
@@ -264,7 +265,7 @@ def _set_element_datetime(self, prop_name, value):
264265
element.set(qn("xsi:type"), "dcterms:W3CDTF")
265266
del self.attrib[qn("xsi:foo")]
266267

267-
def _set_element_text(self, prop_name, value):
268+
def _set_element_text(self, prop_name: str, value: Any) -> None:
268269
"""Set string value of `name` property to `value`."""
269270
if not isinstance(value, str):
270271
value = str(value)
@@ -275,7 +276,7 @@ def _set_element_text(self, prop_name, value):
275276
element = self._get_or_add(prop_name)
276277
element.text = value
277278

278-
def _text_of_element(self, property_name):
279+
def _text_of_element(self, property_name: str) -> str:
279280
"""The text in the element matching `property_name`.
280281
281282
The empty string if the element is not present or contains no text.

src/docx/oxml/ns.py

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Namespace-related objects."""
22

3+
from typing import Any, Dict
4+
5+
from typing_extensions import Self
6+
37
nsmap = {
48
"a": "http://schemas.openxmlformats.org/drawingml/2006/main",
59
"c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
@@ -25,74 +29,80 @@
2529
class NamespacePrefixedTag(str):
2630
"""Value object that knows the semantics of an XML tag having a namespace prefix."""
2731

28-
def __new__(cls, nstag, *args):
32+
def __new__(cls, nstag: str, *args: Any):
2933
return super(NamespacePrefixedTag, cls).__new__(cls, nstag)
3034

31-
def __init__(self, nstag):
35+
def __init__(self, nstag: str):
3236
self._pfx, self._local_part = nstag.split(":")
3337
self._ns_uri = nsmap[self._pfx]
3438

3539
@property
36-
def clark_name(self):
40+
def clark_name(self) -> str:
3741
return "{%s}%s" % (self._ns_uri, self._local_part)
3842

3943
@classmethod
40-
def from_clark_name(cls, clark_name):
44+
def from_clark_name(cls, clark_name: str) -> Self:
4145
nsuri, local_name = clark_name[1:].split("}")
4246
nstag = "%s:%s" % (pfxmap[nsuri], local_name)
4347
return cls(nstag)
4448

4549
@property
46-
def local_part(self):
47-
"""Return the local part of the tag as a string.
50+
def local_part(self) -> str:
51+
"""The local part of this tag.
4852
49-
E.g. 'foobar' is returned for tag 'f:foobar'.
53+
E.g. "foobar" is returned for tag "f:foobar".
5054
"""
5155
return self._local_part
5256

5357
@property
54-
def nsmap(self):
55-
"""Return a dict having a single member, mapping the namespace prefix of this
56-
tag to it's namespace name (e.g. {'f': 'http://foo/bar'}).
58+
def nsmap(self) -> Dict[str, str]:
59+
"""Single-member dict mapping prefix of this tag to it's namespace name.
5760
58-
This is handy for passing to xpath calls and other uses.
61+
Example: `{"f": "http://foo/bar"}`. This is handy for passing to xpath calls
62+
and other uses.
5963
"""
6064
return {self._pfx: self._ns_uri}
6165

6266
@property
63-
def nspfx(self):
64-
"""Return the string namespace prefix for the tag, e.g. 'f' is returned for tag
65-
'f:foobar'."""
67+
def nspfx(self) -> str:
68+
"""The namespace-prefix for this tag.
69+
70+
For example, "f" is returned for tag "f:foobar".
71+
"""
6672
return self._pfx
6773

6874
@property
69-
def nsuri(self):
70-
"""Return the namespace URI for the tag, e.g. 'http://foo/bar' would be returned
71-
for tag 'f:foobar' if the 'f' prefix maps to 'http://foo/bar' in nsmap."""
75+
def nsuri(self) -> str:
76+
"""The namespace URI for this tag.
77+
78+
For example, "http://foo/bar" would be returned for tag "f:foobar" if the "f"
79+
prefix maps to "http://foo/bar" in nsmap.
80+
"""
7281
return self._ns_uri
7382

7483

75-
def nsdecls(*prefixes):
76-
"""Return a string containing a namespace declaration for each of the namespace
77-
prefix strings, e.g. 'p', 'ct', passed as `prefixes`."""
84+
def nsdecls(*prefixes: str) -> str:
85+
"""Namespace declaration including each namespace-prefix in `prefixes`.
86+
87+
Handy for adding required namespace declarations to a tree root element.
88+
"""
7889
return " ".join(['xmlns:%s="%s"' % (pfx, nsmap[pfx]) for pfx in prefixes])
7990

8091

81-
def nspfxmap(*nspfxs):
82-
"""Return a dict containing the subset namespace prefix mappings specified by
83-
`nspfxs`.
92+
def nspfxmap(*nspfxs: str) -> Dict[str, str]:
93+
"""Subset namespace-prefix mappings specified by *nspfxs*.
8494
85-
Any number of namespace prefixes can be supplied, e.g. namespaces('a', 'r', 'p').
95+
Any number of namespace prefixes can be supplied, e.g. namespaces("a", "r", "p").
8696
"""
8797
return {pfx: nsmap[pfx] for pfx in nspfxs}
8898

8999

90-
def qn(tag):
91-
"""Stands for "qualified name", a utility function to turn a namespace prefixed tag
92-
name into a Clark-notation qualified tag name for lxml.
100+
def qn(tag: str) -> str:
101+
"""Stands for "qualified name".
93102
94-
For
95-
example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``.
103+
This utility function converts a familiar namespace-prefixed tag name like "w:p"
104+
into a Clark-notation qualified tag name for lxml. For example, `qn("w:p")` returns
105+
"{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p".
96106
"""
97107
prefix, tagroot = tag.split(":")
98108
uri = nsmap[prefix]

src/docx/oxml/shape.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Custom element classes for shape-related elements like `<w:inline>`."""
22

3+
from __future__ import annotations
4+
35
from docx.oxml import parse_xml
46
from docx.oxml.ns import nsdecls
57
from docx.oxml.simpletypes import (

src/docx/oxml/text/paragraph.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,48 @@
11
"""Custom element classes related to paragraphs (CT_P)."""
22

3-
from docx.oxml.ns import qn
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, Callable, List
6+
47
from docx.oxml.xmlchemy import BaseOxmlElement, OxmlElement, ZeroOrMore, ZeroOrOne
58

9+
if TYPE_CHECKING:
10+
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
11+
from docx.oxml.text.parfmt import CT_PPr
12+
from docx.oxml.text.run import CT_R
13+
614

715
class CT_P(BaseOxmlElement):
816
"""`<w:p>` element, containing the properties and text for a paragraph."""
917

10-
pPr = ZeroOrOne("w:pPr")
11-
r = ZeroOrMore("w:r")
18+
get_or_add_pPr: Callable[[], CT_PPr]
19+
r_lst: List[CT_R]
1220

13-
def _insert_pPr(self, pPr):
14-
self.insert(0, pPr)
15-
return pPr
21+
pPr: CT_PPr | None = ZeroOrOne("w:pPr") # pyright: ignore[reportGeneralTypeIssues]
22+
r = ZeroOrMore("w:r")
1623

17-
def add_p_before(self):
24+
def add_p_before(self) -> CT_P:
1825
"""Return a new `<w:p>` element inserted directly prior to this one."""
1926
new_p = OxmlElement("w:p")
2027
self.addprevious(new_p)
2128
return new_p
2229

2330
@property
24-
def alignment(self):
31+
def alignment(self) -> WD_PARAGRAPH_ALIGNMENT | None:
2532
"""The value of the `<w:jc>` grandchild element or |None| if not present."""
2633
pPr = self.pPr
2734
if pPr is None:
2835
return None
2936
return pPr.jc_val
3037

3138
@alignment.setter
32-
def alignment(self, value):
39+
def alignment(self, value: WD_PARAGRAPH_ALIGNMENT):
3340
pPr = self.get_or_add_pPr()
3441
pPr.jc_val = value
3542

3643
def clear_content(self):
3744
"""Remove all child elements, except the `<w:pPr>` element if present."""
38-
for child in self[:]:
39-
if child.tag == qn("w:pPr"):
40-
continue
45+
for child in self.xpath("./*[not(self::w:pPr)]"):
4146
self.remove(child)
4247

4348
def set_sectPr(self, sectPr):
@@ -47,7 +52,7 @@ def set_sectPr(self, sectPr):
4752
pPr._insert_sectPr(sectPr)
4853

4954
@property
50-
def style(self):
55+
def style(self) -> str | None:
5156
"""String contained in `w:val` attribute of `./w:pPr/w:pStyle` grandchild.
5257
5358
|None| if not present.
@@ -61,3 +66,7 @@ def style(self):
6166
def style(self, style):
6267
pPr = self.get_or_add_pPr()
6368
pPr.style = style
69+
70+
def _insert_pPr(self, pPr: CT_PPr) -> CT_PPr:
71+
self.insert(0, pPr)
72+
return pPr

0 commit comments

Comments
 (0)
0