Skip to content

Commit 523328c

Browse files
committed
rfctr: improve xmlchemy typing
1 parent a1c6b4f commit 523328c

File tree

4 files changed

+180
-105
lines changed

4 files changed

+180
-105
lines changed

src/docx/enum/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ def from_xml(cls, xml_value: str | None) -> Self:
6767
@classmethod
6868
def to_xml(cls: Type[_T], value: int | _T | None) -> str | None:
6969
"""XML value of this enum member, generally an XML attribute value."""
70-
return cls(value).xml_value
70+
# -- presence of multi-arg `__new__()` method fools type-checker, but getting a
71+
# -- member by its value using EnumCls(val) works as usual.
72+
return cls(value).xml_value # pyright: ignore[reportGeneralTypeIssues]
7173

7274

7375
class DocsPageFormatter:

src/docx/oxml/simpletypes.py

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# pyright: reportImportCycles=false
2+
13
"""Simple-type classes, corresponding to ST_* schema items.
24
35
These provide validation and format translation for values stored in XML element
@@ -7,40 +9,49 @@
79

810
from __future__ import annotations
911

10-
from typing import TYPE_CHECKING, Any
12+
from typing import TYPE_CHECKING, Any, Tuple
1113

1214
from docx.exceptions import InvalidXmlError
1315
from docx.shared import Emu, Pt, RGBColor, Twips
1416

1517
if TYPE_CHECKING:
16-
from docx import types as t
1718
from docx.shared import Length
1819

1920

2021
class BaseSimpleType:
2122
"""Base class for simple-types."""
2223

2324
@classmethod
24-
def from_xml(cls, xml_value: str):
25+
def from_xml(cls, xml_value: str) -> Any:
2526
return cls.convert_from_xml(xml_value)
2627

2728
@classmethod
28-
def to_xml(cls, value):
29+
def to_xml(cls, value: Any) -> str:
2930
cls.validate(value)
3031
str_value = cls.convert_to_xml(value)
3132
return str_value
3233

3334
@classmethod
34-
def convert_from_xml(cls, str_value: str) -> t.AbstractSimpleTypeMember:
35+
def convert_from_xml(cls, str_value: str) -> Any:
3536
return int(str_value)
3637

3738
@classmethod
38-
def validate_int(cls, value):
39+
def convert_to_xml(cls, value: Any) -> str:
40+
...
41+
42+
@classmethod
43+
def validate(cls, value: Any) -> None:
44+
...
45+
46+
@classmethod
47+
def validate_int(cls, value: object):
3948
if not isinstance(value, int):
4049
raise TypeError("value must be <type 'int'>, got %s" % type(value))
4150

4251
@classmethod
43-
def validate_int_in_range(cls, value, min_inclusive, max_inclusive):
52+
def validate_int_in_range(
53+
cls, value: int, min_inclusive: int, max_inclusive: int
54+
) -> None:
4455
cls.validate_int(value)
4556
if value < min_inclusive or value > max_inclusive:
4657
raise ValueError(
@@ -57,15 +68,15 @@ def validate_string(cls, value: Any) -> str:
5768

5869
class BaseIntType(BaseSimpleType):
5970
@classmethod
60-
def convert_from_xml(cls, str_value):
71+
def convert_from_xml(cls, str_value: str) -> int:
6172
return int(str_value)
6273

6374
@classmethod
64-
def convert_to_xml(cls, value):
75+
def convert_to_xml(cls, value: int) -> str:
6576
return str(value)
6677

6778
@classmethod
68-
def validate(cls, value):
79+
def validate(cls, value: Any) -> None:
6980
cls.validate_int(value)
7081

7182

@@ -84,34 +95,38 @@ def validate(cls, value: str):
8495

8596

8697
class BaseStringEnumerationType(BaseStringType):
98+
_members: Tuple[str, ...]
99+
87100
@classmethod
88-
def validate(cls, value):
101+
def validate(cls, value: Any) -> None:
89102
cls.validate_string(value)
90103
if value not in cls._members:
91104
raise ValueError("must be one of %s, got '%s'" % (cls._members, value))
92105

93106

94107
class XsdAnyUri(BaseStringType):
95-
"""There's a regular expression this is supposed to meet but so far thinking
96-
spending cycles on validating wouldn't be worth it for the number of programming
97-
errors it would catch."""
108+
"""There's a regex in the spec this is supposed to meet...
109+
110+
but current assessment is that spending cycles on validating wouldn't be worth it
111+
for the number of programming errors it would catch.
112+
"""
98113

99114

100115
class XsdBoolean(BaseSimpleType):
101116
@classmethod
102-
def convert_from_xml(cls, str_value):
117+
def convert_from_xml(cls, str_value: str) -> bool:
103118
if str_value not in ("1", "0", "true", "false"):
104119
raise InvalidXmlError(
105120
"value must be one of '1', '0', 'true' or 'false', got '%s'" % str_value
106121
)
107122
return str_value in ("1", "true")
108123

109124
@classmethod
110-
def convert_to_xml(cls, value):
125+
def convert_to_xml(cls, value: bool) -> str:
111126
return {True: "1", False: "0"}[value]
112127

113128
@classmethod
114-
def validate(cls, value):
129+
def validate(cls, value: Any) -> None:
115130
if value not in (True, False):
116131
raise TypeError(
117132
"only True or False (and possibly None) may be assigned, got"
@@ -130,13 +145,13 @@ class XsdId(BaseStringType):
130145

131146
class XsdInt(BaseIntType):
132147
@classmethod
133-
def validate(cls, value):
148+
def validate(cls, value: Any) -> None:
134149
cls.validate_int_in_range(value, -2147483648, 2147483647)
135150

136151

137152
class XsdLong(BaseIntType):
138153
@classmethod
139-
def validate(cls, value):
154+
def validate(cls, value: Any) -> None:
140155
cls.validate_int_in_range(value, -9223372036854775808, 9223372036854775807)
141156

142157

@@ -157,13 +172,13 @@ class XsdToken(BaseStringType):
157172

158173
class XsdUnsignedInt(BaseIntType):
159174
@classmethod
160-
def validate(cls, value):
175+
def validate(cls, value: Any) -> None:
161176
cls.validate_int_in_range(value, 0, 4294967295)
162177

163178

164179
class XsdUnsignedLong(BaseIntType):
165180
@classmethod
166-
def validate(cls, value):
181+
def validate(cls, value: Any) -> None:
167182
cls.validate_int_in_range(value, 0, 18446744073709551615)
168183

169184

@@ -178,7 +193,7 @@ def validate(cls, value: str) -> None:
178193

179194
class ST_BrType(XsdString):
180195
@classmethod
181-
def validate(cls, value):
196+
def validate(cls, value: Any) -> None:
182197
cls.validate_string(value)
183198
valid_values = ("page", "column", "textWrapping")
184199
if value not in valid_values:
@@ -187,19 +202,19 @@ def validate(cls, value):
187202

188203
class ST_Coordinate(BaseIntType):
189204
@classmethod
190-
def convert_from_xml(cls, str_value):
205+
def convert_from_xml(cls, str_value: str) -> Length:
191206
if "i" in str_value or "m" in str_value or "p" in str_value:
192207
return ST_UniversalMeasure.convert_from_xml(str_value)
193208
return Emu(int(str_value))
194209

195210
@classmethod
196-
def validate(cls, value):
211+
def validate(cls, value: Any) -> None:
197212
ST_CoordinateUnqualified.validate(value)
198213

199214

200215
class ST_CoordinateUnqualified(XsdLong):
201216
@classmethod
202-
def validate(cls, value):
217+
def validate(cls, value: Any) -> None:
203218
cls.validate_int_in_range(value, -27273042329600, 27273042316900)
204219

205220

@@ -213,19 +228,23 @@ class ST_DrawingElementId(XsdUnsignedInt):
213228

214229
class ST_HexColor(BaseStringType):
215230
@classmethod
216-
def convert_from_xml(cls, str_value):
231+
def convert_from_xml( # pyright: ignore[reportIncompatibleMethodOverride]
232+
cls, str_value: str
233+
) -> RGBColor | str:
217234
if str_value == "auto":
218235
return ST_HexColorAuto.AUTO
219236
return RGBColor.from_string(str_value)
220237

221238
@classmethod
222-
def convert_to_xml(cls, value):
239+
def convert_to_xml( # pyright: ignore[reportIncompatibleMethodOverride]
240+
cls, value: RGBColor
241+
) -> str:
223242
"""Keep alpha hex numerals all uppercase just for consistency."""
224243
# expecting 3-tuple of ints in range 0-255
225244
return "%02X%02X%02X" % value
226245

227246
@classmethod
228-
def validate(cls, value):
247+
def validate(cls, value: Any) -> None:
229248
# must be an RGBColor object ---
230249
if not isinstance(value, RGBColor):
231250
raise ValueError(
@@ -269,7 +288,7 @@ class ST_Merge(XsdStringEnumeration):
269288

270289
class ST_OnOff(XsdBoolean):
271290
@classmethod
272-
def convert_from_xml(cls, str_value):
291+
def convert_from_xml(cls, str_value: str) -> bool:
273292
if str_value not in ("1", "0", "true", "false", "on", "off"):
274293
raise InvalidXmlError(
275294
"value must be one of '1', '0', 'true', 'false', 'on', or 'o"
@@ -280,11 +299,11 @@ def convert_from_xml(cls, str_value):
280299

281300
class ST_PositiveCoordinate(XsdLong):
282301
@classmethod
283-
def convert_from_xml(cls, str_value):
302+
def convert_from_xml(cls, str_value: str) -> Length:
284303
return Emu(int(str_value))
285304

286305
@classmethod
287-
def validate(cls, value):
306+
def validate(cls, value: Any) -> None:
288307
cls.validate_int_in_range(value, 0, 27273042316900)
289308

290309

@@ -294,13 +313,13 @@ class ST_RelationshipId(XsdString):
294313

295314
class ST_SignedTwipsMeasure(XsdInt):
296315
@classmethod
297-
def convert_from_xml(cls, str_value):
316+
def convert_from_xml(cls, str_value: str) -> Length:
298317
if "i" in str_value or "m" in str_value or "p" in str_value:
299318
return ST_UniversalMeasure.convert_from_xml(str_value)
300319
return Twips(int(str_value))
301320

302321
@classmethod
303-
def convert_to_xml(cls, value):
322+
def convert_to_xml(cls, value: int | Length) -> str:
304323
emu = Emu(value)
305324
twips = emu.twips
306325
return str(twips)
@@ -312,7 +331,7 @@ class ST_String(XsdString):
312331

313332
class ST_TblLayoutType(XsdString):
314333
@classmethod
315-
def validate(cls, value):
334+
def validate(cls, value: Any) -> None:
316335
cls.validate_string(value)
317336
valid_values = ("fixed", "autofit")
318337
if value not in valid_values:
@@ -321,7 +340,7 @@ def validate(cls, value):
321340

322341
class ST_TblWidth(XsdString):
323342
@classmethod
324-
def validate(cls, value):
343+
def validate(cls, value: Any) -> None:
325344
cls.validate_string(value)
326345
valid_values = ("auto", "dxa", "nil", "pct")
327346
if value not in valid_values:
@@ -330,13 +349,13 @@ def validate(cls, value):
330349

331350
class ST_TwipsMeasure(XsdUnsignedLong):
332351
@classmethod
333-
def convert_from_xml(cls, str_value):
352+
def convert_from_xml(cls, str_value: str) -> Length:
334353
if "i" in str_value or "m" in str_value or "p" in str_value:
335354
return ST_UniversalMeasure.convert_from_xml(str_value)
336355
return Twips(int(str_value))
337356

338357
@classmethod
339-
def convert_to_xml(cls, value):
358+
def convert_to_xml(cls, value: int | Length) -> str:
340359
emu = Emu(value)
341360
twips = emu.twips
342361
return str(twips)

0 commit comments

Comments
 (0)