Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit afa670a

Browse files
committed
modn: modernize tests
- inline single-test fixtures - remove obsolete pre-cxml XML builders
1 parent 4262f4d commit afa670a

File tree

21 files changed

+1180
-1347
lines changed

21 files changed

+1180
-1347
lines changed

src/docx/dml/color.py

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,95 @@
11
"""DrawingML objects related to color, ColorFormat being the most prominent."""
22

3-
from ..enum.dml import MSO_COLOR_TYPE
4-
from ..oxml.simpletypes import ST_HexColorAuto
5-
from ..shared import ElementProxy
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING, cast
6+
7+
from typing_extensions import TypeAlias
8+
9+
from docx.enum.dml import MSO_COLOR_TYPE
10+
from docx.oxml.simpletypes import ST_HexColorAuto
11+
from docx.shared import ElementProxy, RGBColor
12+
13+
if TYPE_CHECKING:
14+
from docx.enum.dml import MSO_THEME_COLOR
15+
from docx.oxml.text.font import CT_Color
16+
from docx.oxml.text.run import CT_R
17+
18+
# -- other element types can be a parent of an `w:rPr` element, but for now only `w:r` is --
19+
RPrParent: TypeAlias = "CT_R"
620

721

822
class ColorFormat(ElementProxy):
9-
"""Provides access to color settings such as RGB color, theme color, and luminance
10-
adjustments."""
23+
"""Provides access to color settings like RGB color, theme color, and luminance adjustments."""
1124

12-
def __init__(self, rPr_parent):
25+
def __init__(self, rPr_parent: RPrParent):
1326
super(ColorFormat, self).__init__(rPr_parent)
27+
self._element = rPr_parent
1428

1529
@property
16-
def rgb(self):
30+
def rgb(self) -> RGBColor | None:
1731
"""An |RGBColor| value or |None| if no RGB color is specified.
1832
19-
When :attr:`type` is `MSO_COLOR_TYPE.RGB`, the value of this property will
20-
always be an |RGBColor| value. It may also be an |RGBColor| value if
21-
:attr:`type` is `MSO_COLOR_TYPE.THEME`, as Word writes the current value of a
22-
theme color when one is assigned. In that case, the RGB value should be
23-
interpreted as no more than a good guess however, as the theme color takes
24-
precedence at rendering time. Its value is |None| whenever :attr:`type` is
25-
either |None| or `MSO_COLOR_TYPE.AUTO`.
26-
27-
Assigning an |RGBColor| value causes :attr:`type` to become `MSO_COLOR_TYPE.RGB`
28-
and any theme color is removed. Assigning |None| causes any color to be removed
29-
such that the effective color is inherited from the style hierarchy.
33+
When :attr:`type` is `MSO_COLOR_TYPE.RGB`, the value of this property will always be an
34+
|RGBColor| value. It may also be an |RGBColor| value if :attr:`type` is
35+
`MSO_COLOR_TYPE.THEME`, as Word writes the current value of a theme color when one is
36+
assigned. In that case, the RGB value should be interpreted as no more than a good guess
37+
however, as the theme color takes precedence at rendering time. Its value is |None|
38+
whenever :attr:`type` is either |None| or `MSO_COLOR_TYPE.AUTO`.
39+
40+
Assigning an |RGBColor| value causes :attr:`type` to become `MSO_COLOR_TYPE.RGB` and any
41+
theme color is removed. Assigning |None| causes any color to be removed such that the
42+
effective color is inherited from the style hierarchy.
3043
"""
3144
color = self._color
3245
if color is None:
3346
return None
3447
if color.val == ST_HexColorAuto.AUTO:
3548
return None
36-
return color.val
49+
return cast(RGBColor, color.val)
3750

3851
@rgb.setter
39-
def rgb(self, value):
52+
def rgb(self, value: RGBColor | None):
4053
if value is None and self._color is None:
4154
return
4255
rPr = self._element.get_or_add_rPr()
43-
rPr._remove_color()
56+
rPr._remove_color() # pyright: ignore[reportPrivateUsage]
4457
if value is not None:
4558
rPr.get_or_add_color().val = value
4659

4760
@property
48-
def theme_color(self):
61+
def theme_color(self) -> MSO_THEME_COLOR | None:
4962
"""Member of :ref:`MsoThemeColorIndex` or |None| if no theme color is specified.
5063
51-
When :attr:`type` is `MSO_COLOR_TYPE.THEME`, the value of this property will
52-
always be a member of :ref:`MsoThemeColorIndex`. When :attr:`type` has any other
53-
value, the value of this property is |None|.
64+
When :attr:`type` is `MSO_COLOR_TYPE.THEME`, the value of this property will always be a
65+
member of :ref:`MsoThemeColorIndex`. When :attr:`type` has any other value, the value of
66+
this property is |None|.
5467
5568
Assigning a member of :ref:`MsoThemeColorIndex` causes :attr:`type` to become
56-
`MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word.
57-
Assigning |None| causes any color specification to be removed such that the
58-
effective color is inherited from the style hierarchy.
69+
`MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word. Assigning
70+
|None| causes any color specification to be removed such that the effective color is
71+
inherited from the style hierarchy.
5972
"""
6073
color = self._color
61-
if color is None or color.themeColor is None:
74+
if color is None:
6275
return None
6376
return color.themeColor
6477

6578
@theme_color.setter
66-
def theme_color(self, value):
79+
def theme_color(self, value: MSO_THEME_COLOR | None):
6780
if value is None:
68-
if self._color is not None:
69-
self._element.rPr._remove_color()
81+
if self._color is not None and self._element.rPr is not None:
82+
self._element.rPr._remove_color() # pyright: ignore[reportPrivateUsage]
7083
return
7184
self._element.get_or_add_rPr().get_or_add_color().themeColor = value
7285

7386
@property
74-
def type(self) -> MSO_COLOR_TYPE:
87+
def type(self) -> MSO_COLOR_TYPE | None:
7588
"""Read-only.
7689
77-
A member of :ref:`MsoColorType`, one of RGB, THEME, or AUTO, corresponding to
78-
the way this color is defined. Its value is |None| if no color is applied at
79-
this level, which causes the effective color to be inherited from the style
80-
hierarchy.
90+
A member of :ref:`MsoColorType`, one of RGB, THEME, or AUTO, corresponding to the way this
91+
color is defined. Its value is |None| if no color is applied at this level, which causes
92+
the effective color to be inherited from the style hierarchy.
8193
"""
8294
color = self._color
8395
if color is None:
@@ -89,7 +101,7 @@ def type(self) -> MSO_COLOR_TYPE:
89101
return MSO_COLOR_TYPE.RGB
90102

91103
@property
92-
def _color(self):
104+
def _color(self) -> CT_Color | None:
93105
"""Return `w:rPr/w:color` or |None| if not present.
94106
95107
Helper to factor out repetitive element access.

src/docx/document.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111
from docx.enum.section import WD_SECTION
1212
from docx.enum.text import WD_BREAK
1313
from docx.section import Section, Sections
14-
from docx.shared import ElementProxy, Emu
14+
from docx.shared import ElementProxy, Emu, Inches, Length
1515

1616
if TYPE_CHECKING:
1717
import docx.types as t
1818
from docx.oxml.document import CT_Body, CT_Document
1919
from docx.parts.document import DocumentPart
2020
from docx.settings import Settings
21-
from docx.shared import Length
2221
from docx.styles.style import ParagraphStyle, _TableStyle
2322
from docx.table import Table
2423
from docx.text.paragraph import Paragraph
@@ -178,7 +177,10 @@ def tables(self) -> List[Table]:
178177
def _block_width(self) -> Length:
179178
"""A |Length| object specifying the space between margins in last section."""
180179
section = self.sections[-1]
181-
return Emu(section.page_width - section.left_margin - section.right_margin)
180+
page_width = section.page_width or Inches(8.5)
181+
left_margin = section.left_margin or Inches(1)
182+
right_margin = section.right_margin or Inches(1)
183+
return Emu(page_width - left_margin - right_margin)
182184

183185
@property
184186
def _body(self) -> _Body:
@@ -198,7 +200,7 @@ def __init__(self, body_elm: CT_Body, parent: t.ProvidesStoryPart):
198200
super(_Body, self).__init__(body_elm, parent)
199201
self._body = body_elm
200202

201-
def clear_content(self):
203+
def clear_content(self) -> _Body:
202204
"""Return this |_Body| instance after clearing it of all content.
203205
204206
Section properties for the main document story, if present, are preserved.

src/docx/enum/base.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ class BaseXmlEnum(int, enum.Enum):
3737
corresponding member in the MS API enum of the same name.
3838
"""
3939

40-
xml_value: str
40+
xml_value: str | None
4141

42-
def __new__(cls, ms_api_value: int, xml_value: str, docstr: str):
42+
def __new__(cls, ms_api_value: int, xml_value: str | None, docstr: str):
4343
self = int.__new__(cls, ms_api_value)
4444
self._value_ = ms_api_value
4545
self.xml_value = xml_value
@@ -70,7 +70,11 @@ def to_xml(cls: Type[_T], value: int | _T | None) -> str | None:
7070
"""XML value of this enum member, generally an XML attribute value."""
7171
# -- presence of multi-arg `__new__()` method fools type-checker, but getting a
7272
# -- member by its value using EnumCls(val) works as usual.
73-
return cls(value).xml_value
73+
member = cls(value)
74+
xml_value = member.xml_value
75+
if not xml_value:
76+
raise ValueError(f"{cls.__name__}.{member.name} has no XML representation")
77+
return xml_value
7478

7579

7680
class DocsPageFormatter:

src/docx/opc/package.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from docx.shared import lazyproperty
1515

1616
if TYPE_CHECKING:
17+
from typing_extensions import Self
18+
1719
from docx.opc.coreprops import CoreProperties
1820
from docx.opc.part import Part
1921
from docx.opc.rel import _Relationship # pyright: ignore[reportPrivateUsage]
@@ -26,9 +28,6 @@ class OpcPackage:
2628
to a package file or file-like object containing one.
2729
"""
2830

29-
def __init__(self):
30-
super(OpcPackage, self).__init__()
31-
3231
def after_unmarshal(self):
3332
"""Entry point for any post-unmarshaling processing.
3433
@@ -122,7 +121,7 @@ def next_partname(self, template: str) -> PackURI:
122121
return PackURI(candidate_partname)
123122

124123
@classmethod
125-
def open(cls, pkg_file: str | IO[bytes]) -> OpcPackage:
124+
def open(cls, pkg_file: str | IO[bytes]) -> Self:
126125
"""Return an |OpcPackage| instance loaded with the contents of `pkg_file`."""
127126
pkg_reader = PackageReader.from_file(pkg_file)
128127
package = cls()

src/docx/oxml/text/font.py

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# pyright: reportAssignmentType=false
2+
13
"""Custom element classes related to run properties (font)."""
24

35
from __future__ import annotations
@@ -20,6 +22,7 @@
2022
RequiredAttribute,
2123
ZeroOrOne,
2224
)
25+
from docx.shared import RGBColor
2326

2427
if TYPE_CHECKING:
2528
from docx.oxml.shared import CT_OnOff, CT_String
@@ -29,8 +32,8 @@
2932
class CT_Color(BaseOxmlElement):
3033
"""`w:color` element, specifying the color of a font and perhaps other objects."""
3134

32-
val = RequiredAttribute("w:val", ST_HexColor)
33-
themeColor = OptionalAttribute("w:themeColor", MSO_THEME_COLOR)
35+
val: RGBColor | str = RequiredAttribute("w:val", ST_HexColor)
36+
themeColor: MSO_THEME_COLOR | None = OptionalAttribute("w:themeColor", MSO_THEME_COLOR)
3437

3538

3639
class CT_Fonts(BaseOxmlElement):
@@ -39,39 +42,33 @@ class CT_Fonts(BaseOxmlElement):
3942
Specifies typeface name for the various language types.
4043
"""
4144

42-
ascii: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
43-
"w:ascii", ST_String
44-
)
45-
hAnsi: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
46-
"w:hAnsi", ST_String
47-
)
45+
ascii: str | None = OptionalAttribute("w:ascii", ST_String)
46+
hAnsi: str | None = OptionalAttribute("w:hAnsi", ST_String)
4847

4948

5049
class CT_Highlight(BaseOxmlElement):
5150
"""`w:highlight` element, specifying font highlighting/background color."""
5251

53-
val: WD_COLOR_INDEX = RequiredAttribute( # pyright: ignore[reportGeneralTypeIssues]
54-
"w:val", WD_COLOR_INDEX
55-
)
52+
val: WD_COLOR_INDEX = RequiredAttribute("w:val", WD_COLOR_INDEX)
5653

5754

5855
class CT_HpsMeasure(BaseOxmlElement):
5956
"""Used for `<w:sz>` element and others, specifying font size in half-points."""
6057

61-
val: Length = RequiredAttribute( # pyright: ignore[reportGeneralTypeIssues]
62-
"w:val", ST_HpsMeasure
63-
)
58+
val: Length = RequiredAttribute("w:val", ST_HpsMeasure)
6459

6560

6661
class CT_RPr(BaseOxmlElement):
6762
"""`<w:rPr>` element, containing the properties for a run."""
6863

64+
get_or_add_color: Callable[[], CT_Color]
6965
get_or_add_highlight: Callable[[], CT_Highlight]
7066
get_or_add_rFonts: Callable[[], CT_Fonts]
7167
get_or_add_sz: Callable[[], CT_HpsMeasure]
7268
get_or_add_vertAlign: Callable[[], CT_VerticalAlignRun]
7369
_add_rStyle: Callable[..., CT_String]
7470
_add_u: Callable[[], CT_Underline]
71+
_remove_color: Callable[[], None]
7572
_remove_highlight: Callable[[], None]
7673
_remove_rFonts: Callable[[], None]
7774
_remove_rStyle: Callable[[], None]
@@ -120,15 +117,9 @@ class CT_RPr(BaseOxmlElement):
120117
"w:specVanish",
121118
"w:oMath",
122119
)
123-
rStyle: CT_String | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
124-
"w:rStyle", successors=_tag_seq[1:]
125-
)
126-
rFonts: CT_Fonts | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
127-
"w:rFonts", successors=_tag_seq[2:]
128-
)
129-
b: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
130-
"w:b", successors=_tag_seq[3:]
131-
)
120+
rStyle: CT_String | None = ZeroOrOne("w:rStyle", successors=_tag_seq[1:])
121+
rFonts: CT_Fonts | None = ZeroOrOne("w:rFonts", successors=_tag_seq[2:])
122+
b: CT_OnOff | None = ZeroOrOne("w:b", successors=_tag_seq[3:])
132123
bCs = ZeroOrOne("w:bCs", successors=_tag_seq[4:])
133124
i = ZeroOrOne("w:i", successors=_tag_seq[5:])
134125
iCs = ZeroOrOne("w:iCs", successors=_tag_seq[6:])
@@ -144,19 +135,11 @@ class CT_RPr(BaseOxmlElement):
144135
snapToGrid = ZeroOrOne("w:snapToGrid", successors=_tag_seq[16:])
145136
vanish = ZeroOrOne("w:vanish", successors=_tag_seq[17:])
146137
webHidden = ZeroOrOne("w:webHidden", successors=_tag_seq[18:])
147-
color = ZeroOrOne("w:color", successors=_tag_seq[19:])
148-
sz: CT_HpsMeasure | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
149-
"w:sz", successors=_tag_seq[24:]
150-
)
151-
highlight: CT_Highlight | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
152-
"w:highlight", successors=_tag_seq[26:]
153-
)
154-
u: CT_Underline | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
155-
"w:u", successors=_tag_seq[27:]
156-
)
157-
vertAlign: CT_VerticalAlignRun | None = ZeroOrOne( # pyright: ignore[reportGeneralTypeIssues]
158-
"w:vertAlign", successors=_tag_seq[32:]
159-
)
138+
color: CT_Color | None = ZeroOrOne("w:color", successors=_tag_seq[19:])
139+
sz: CT_HpsMeasure | None = ZeroOrOne("w:sz", successors=_tag_seq[24:])
140+
highlight: CT_Highlight | None = ZeroOrOne("w:highlight", successors=_tag_seq[26:])
141+
u: CT_Underline | None = ZeroOrOne("w:u", successors=_tag_seq[27:])
142+
vertAlign: CT_VerticalAlignRun | None = ZeroOrOne("w:vertAlign", successors=_tag_seq[32:])
160143
rtl = ZeroOrOne("w:rtl", successors=_tag_seq[33:])
161144
cs = ZeroOrOne("w:cs", successors=_tag_seq[34:])
162145
specVanish = ZeroOrOne("w:specVanish", successors=_tag_seq[38:])
@@ -343,14 +326,10 @@ def _set_bool_val(self, name: str, value: bool | None):
343326
class CT_Underline(BaseOxmlElement):
344327
"""`<w:u>` element, specifying the underlining style for a run."""
345328

346-
val: WD_UNDERLINE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
347-
"w:val", WD_UNDERLINE
348-
)
329+
val: WD_UNDERLINE | None = OptionalAttribute("w:val", WD_UNDERLINE)
349330

350331

351332
class CT_VerticalAlignRun(BaseOxmlElement):
352333
"""`<w:vertAlign>` element, specifying subscript or superscript."""
353334

354-
val: str = RequiredAttribute( # pyright: ignore[reportGeneralTypeIssues]
355-
"w:val", ST_VerticalAlignRun
356-
)
335+
val: str = RequiredAttribute("w:val", ST_VerticalAlignRun)

src/docx/parts/document.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,13 @@ def inline_shapes(self):
8989
return InlineShapes(self._element.body, self)
9090

9191
@lazyproperty
92-
def numbering_part(self):
93-
"""A |NumberingPart| object providing access to the numbering definitions for
94-
this document.
92+
def numbering_part(self) -> NumberingPart:
93+
"""A |NumberingPart| object providing access to the numbering definitions for this document.
9594
9695
Creates an empty numbering part if one is not present.
9796
"""
9897
try:
99-
return self.part_related_by(RT.NUMBERING)
98+
return cast(NumberingPart, self.part_related_by(RT.NUMBERING))
10099
except KeyError:
101100
numbering_part = NumberingPart.new()
102101
self.relate_to(numbering_part, RT.NUMBERING)

src/docx/parts/numbering.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ class NumberingPart(XmlPart):
99
or glossary."""
1010

1111
@classmethod
12-
def new(cls):
13-
"""Return newly created empty numbering part, containing only the root
14-
``<w:numbering>`` element."""
12+
def new(cls) -> "NumberingPart":
13+
"""Newly created numbering part, containing only the root ``<w:numbering>`` element."""
1514
raise NotImplementedError
1615

1716
@lazyproperty

0 commit comments

Comments
 (0)