Skip to content

Commit 50008cc

Browse files
Add type annotations to fontshell. (#826)
* Annotate `fontshell`` modules. • Fix related inconsistencies in base classes. * Annotate more modules and correct base classes. * Annotate more modules. * Replace `getNaked` function with `type: ignore` in `naked`. - Various other fixes. * Correct `_get_color` return and mypy errors * Correct mypy errors * Change to `assertCountEqual` for keys in `test_copy` * Correct more mypy errors. * Update error message for None component check --------- Co-authored-by: knutnergaard <15194233+knutnergaard@users.noreply.github.com> Co-authored-by: Ben Kiel <ben@benkiel.com>
1 parent cdafc80 commit 50008cc

25 files changed

+539
-345
lines changed

Lib/fontParts/base/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from fontParts.base import normalizers
2626
from fontParts.base.annotations import (
2727
PairType,
28-
CollectionType,
2928
PairCollectionType,
3029
SextupleCollectionType,
3130
IntFloatType,

Lib/fontParts/base/font.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ def copyData(self, source: BaseFont) -> None:
131131
else:
132132
layer = self.newLayer(layerName)
133133
layer.copyData(source.getLayer(layerName))
134-
for guideline in self.guidelines:
135-
self.appendGuideline(guideline)
134+
for guideline in source.guidelines:
135+
self.appendGuideline(guideline=guideline)
136136
super(BaseFont, self).copyData(source)
137137

138138
# ---------------
@@ -293,7 +293,7 @@ def _save(
293293
self,
294294
path: Optional[str],
295295
showProgress: bool,
296-
formatVersion: Optional[float],
296+
formatVersion: Optional[int],
297297
fileStructure: Optional[str],
298298
**kwargs: Any,
299299
) -> None:
@@ -1828,6 +1828,8 @@ def appendGuideline(
18281828
identifier = normalizedGuideline.identifier
18291829
if position is not None:
18301830
position = normalizers.normalizeCoordinateTuple(position)
1831+
else:
1832+
raise ValueError("Position cannot be None.")
18311833
if angle is not None:
18321834
angle = normalizers.normalizeRotationAngle(angle)
18331835
if name is not None:
@@ -1843,11 +1845,11 @@ def appendGuideline(
18431845

18441846
def _appendGuideline( # type: ignore[return]
18451847
self,
1846-
position: Optional[PairCollectionType[IntFloatType]],
1848+
position: PairCollectionType[IntFloatType],
18471849
angle: Optional[float],
18481850
name: Optional[str],
18491851
color: Optional[QuadrupleCollectionType[IntFloatType]],
1850-
**kwargs,
1852+
**kwargs: Any,
18511853
) -> BaseGuideline:
18521854
r"""Append a new guideline to the native font.
18531855

Lib/fontParts/base/glyph.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ def _set_base_unicodes(self, value: CollectionType[int]) -> None:
308308
value = normalizers.normalizeGlyphUnicodes(value)
309309
self._set_unicodes(value)
310310

311-
def _get_unicodes(self) -> Tuple[int, ...]: # type: ignore[return]
311+
def _get_unicodes(self) -> CollectionType[int]: # type: ignore[return]
312312
"""Get the Unicode values assigned to the glyph.
313313
314314
This is the environment implementation of
@@ -3226,16 +3226,18 @@ def _removeLayer(self, name: str, **kwargs: Any) -> None:
32263226
""",
32273227
)
32283228

3229-
def _get_base_image(self) -> BaseImage:
3229+
def _get_base_image(self) -> Optional[BaseImage]:
32303230
image = self._get_image()
3231-
if image.glyph is None:
3232-
image.glyph = self
3231+
if image is not None:
3232+
if image.glyph is None:
3233+
image.glyph = self
32333234
return image
32343235

3235-
def _get_image(self) -> BaseImage: # type: ignore[return]
3236+
def _get_image(self) -> Optional[BaseImage]: # type: ignore[return]
32363237
"""Get the image for the native glyph.
32373238
3238-
:return: The :class:`BaseImage` subclass instance belonging to the glyph.
3239+
:return: The :class:`BaseImage` subclass instance belonging to the glyph,
3240+
or :obj:`None` if the glyph has no image.
32393241
:raises NotImplementedError: If the method has not been overridden by a
32403242
subclass.
32413243

Lib/fontParts/base/groups.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
if TYPE_CHECKING:
1111
from fontParts.base.font import BaseFont
12-
from fontparts.base import BaseItems
13-
from fontparts.base import BaseKeys
14-
from fontparts.base import BaseValues
12+
from fontParts.base.base import BaseKeys
13+
from fontParts.base.base import BaseItems
14+
from fontParts.base.base import BaseValues
1515

1616
ValueType = Tuple[str, ...]
1717
GroupsDict = Dict[str, ValueType]
@@ -410,7 +410,7 @@ def get(
410410
"""
411411
return super(BaseGroups, self).get(groupName, default)
412412

413-
def items(self) -> BaseItems[Tuple[str, ValueType]]:
413+
def items(self) -> BaseItems[str, ValueType]:
414414
"""Return the items in the current groups instance.
415415
416416
Each item is represented as a :class:`tuple` of key-value pairs, where:

Lib/fontParts/base/guideline.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ def _set_name(self, value: Optional[str]) -> None:
535535
""",
536536
)
537537

538-
def _get_base_color(self) -> Optional[Color]:
538+
def _get_base_color(self) -> Optional[QuadrupleType[float]]:
539539
value = self._get_color()
540540
if value is not None:
541541
value = Color(value)
@@ -548,7 +548,7 @@ def _set_base_color(
548548
value = normalizers.normalizeColor(value)
549549
self._set_color(value)
550550

551-
def _get_color(self) -> Optional[QuadrupleCollectionType[IntFloatType]]:
551+
def _get_color(self) -> Optional[QuadrupleType[float]]:
552552
"""Get the native guideline's color.
553553
554554
This is the environment implementation of the :attr:`BaseGuideline.color`

Lib/fontParts/base/image.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,24 +420,26 @@ def _set_color(self, value: Optional[QuadrupleType[float]]) -> None:
420420
The possible formats are defined by each environment.
421421
The value must be a :class:`bytes` object.
422422
423-
:return: A :class:`bytes` object representing the raw byte data of the image.
423+
:return: A :class:`bytes` object representing the raw byte data of the image
424+
or :obj:`None`.
424425
425426
""",
426427
)
427428

428-
def _get_base_data(self) -> bytes:
429+
def _get_base_data(self) -> Optional[bytes]:
429430
return self._get_data()
430431

431432
def _set_base_data(self, value: bytes) -> None:
432433
self._set_data(value)
433434

434-
def _get_data(self) -> bytes:
435+
def _get_data(self) -> Optional[bytes]:
435436
"""Get the native image's raw byte data.
436437
437438
This is the environment implementation of the :attr:`BaseImage.data`
438439
property getter.
439440
440-
:return: A :class:`bytes` object representing the data of the image.
441+
:return: A :class:`bytes` object representing the raw byte data of the image
442+
or :obj:`None`.
441443
:raises NotImplementedError: If the method has not been overridden by a
442444
subclass.
443445

Lib/fontParts/base/layer.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -804,18 +804,20 @@ def _set_name(self, value: str, **kwargs: Any) -> None:
804804
""",
805805
)
806806

807-
def _get_base_color(self) -> QuadrupleCollectionType[IntFloatType]:
807+
def _get_base_color(self) -> Optional[QuadrupleCollectionType[IntFloatType]]:
808808
value = self._get_color()
809809
if value is not None:
810810
value = Color(value)
811811
return value
812812

813-
def _set_base_color(self, value: QuadrupleCollectionType[IntFloatType]) -> None:
813+
def _set_base_color(
814+
self, value: Optional[QuadrupleCollectionType[IntFloatType]]
815+
) -> None:
814816
if value is not None:
815817
value = normalizers.normalizeColor(value)
816818
self._set_color(value)
817819

818-
def _get_color(self) -> QuadrupleCollectionType[IntFloatType]: # type: ignore[return]
820+
def _get_color(self) -> Optional[QuadrupleCollectionType[IntFloatType]]: # type: ignore[return]
819821
"""Get the color of the layer.
820822
821823
This is the environment implementation of
@@ -835,7 +837,7 @@ def _get_color(self) -> QuadrupleCollectionType[IntFloatType]: # type: ignore[r
835837
self.raiseNotImplementedError()
836838

837839
def _set_color(
838-
self, value: QuadrupleCollectionType[IntFloatType], **kwargs: Any
840+
self, value: Optional[QuadrupleCollectionType[IntFloatType]], **kwargs: Any
839841
) -> None:
840842
r"""Get or set the color of the layer.
841843

Lib/fontParts/base/lib.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
if TYPE_CHECKING:
1111
from fontParts.base.glyph import BaseGlyph
1212
from fontParts.base.font import BaseFont
13-
from fontparts.base.layer import BaseLayer
14-
from fontparts.base import BaseItems
15-
from fontparts.base import BaseKeys
16-
from fontparts.base import BaseValues
13+
from fontParts.base.layer import BaseLayer
14+
from fontParts.base.base import BaseItems
15+
from fontParts.base.base import BaseValues
16+
from fontParts.base.base import BaseKeys
1717

1818

1919
class BaseLib(BaseDict, DeprecatedLib, RemovedLib):

Lib/fontParts/fontshell/anchor.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
1+
from __future__ import annotations
2+
from typing import Optional
3+
14
import defcon
25
from fontParts.base import BaseAnchor
6+
from fontParts.base.annotations import (
7+
QuadrupleType,
8+
QuadrupleCollectionType,
9+
IntFloatType,
10+
)
311
from fontParts.fontshell.base import RBaseObject
412

513

614
class RAnchor(RBaseObject, BaseAnchor):
715
wrapClass = defcon.Anchor
816

9-
def _init(self, wrap=None):
10-
if wrap is None:
11-
wrap = self.wrapClass()
12-
wrap.x = 0
13-
wrap.y = 0
14-
super(RAnchor, self)._init(wrap=wrap)
17+
def _init(self, pathOrObject: Optional[defcon.Anchor] = None) -> None:
18+
if self.wrapClass is not None:
19+
if pathOrObject is None:
20+
pathOrObject = self.wrapClass()
21+
pathOrObject.x = 0
22+
pathOrObject.y = 0
23+
super(RAnchor, self)._init(pathOrObject=pathOrObject)
1524

1625
# --------
1726
# Position
1827
# --------
1928

2029
# x
2130

22-
def _get_x(self):
31+
def _get_x(self) -> float:
2332
return self.naked().x
2433

25-
def _set_x(self, value):
34+
def _set_x(self, value: float) -> None:
2635
self.naked().x = value
2736

2837
# y
2938

30-
def _get_y(self):
39+
def _get_y(self) -> float:
3140
return self.naked().y
3241

33-
def _set_y(self, value):
42+
def _set_y(self, value: float) -> None:
3443
self.naked().y = value
3544

3645
# --------------
@@ -39,32 +48,32 @@ def _set_y(self, value):
3948

4049
# identifier
4150

42-
def _get_identifier(self):
43-
anchor = self.naked()
44-
return anchor.identifier
51+
def _get_identifier(self) -> Optional[str]:
52+
return self.naked().identifier
4553

46-
def _getIdentifier(self):
47-
anchor = self.naked()
48-
return anchor.generateIdentifier()
54+
def _getIdentifier(self) -> str:
55+
return self.naked().generateIdentifier()
4956

50-
def _setIdentifier(self, value):
57+
def _setIdentifier(self, value: str) -> None:
5158
self.naked().identifier = value
5259

5360
# name
5461

55-
def _get_name(self):
62+
def _get_name(self) -> Optional[str]:
5663
return self.naked().name
5764

58-
def _set_name(self, value):
65+
def _set_name(self, value: Optional[str]) -> None:
5966
self.naked().name = value
6067

6168
# color
6269

63-
def _get_color(self):
70+
def _get_color(self) -> Optional[QuadrupleType[float]]:
6471
value = self.naked().color
6572
if value is not None:
6673
value = tuple(value)
6774
return value
6875

69-
def _set_color(self, value):
76+
def _set_color(
77+
self, value: Optional[QuadrupleCollectionType[IntFloatType]]
78+
) -> None:
7079
self.naked().color = value

Lib/fontParts/fontshell/base.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
class RBaseObject(object):
2-
wrapClass = None
1+
from __future__ import annotations
2+
from typing import Generic, Optional, Type, TypeVar
33

4-
def _init(self, wrap=None):
5-
if wrap is None and self.wrapClass is not None:
6-
wrap = self.wrapClass()
7-
if wrap is not None:
8-
self._wrapped = wrap
4+
RBaseObjectType = TypeVar("RBaseObjectType", bound="RBaseObject")
95

10-
def changed(self):
6+
7+
class RBaseObject(Generic[RBaseObjectType]):
8+
wrapClass: Optional[Type[RBaseObjectType]] = None
9+
dirty: bool
10+
11+
def _init(self, pathOrObject: Optional[RBaseObjectType] = None) -> None:
12+
if pathOrObject is None and self.wrapClass is not None:
13+
pathOrObject = self.wrapClass() # pylint: disable=E1102
14+
if pathOrObject is not None:
15+
self._wrapped = pathOrObject
16+
17+
def changed(self) -> None:
1118
self.naked().dirty = True
1219

13-
def naked(self):
20+
def naked(self) -> RBaseObjectType:
1421
if hasattr(self, "_wrapped"):
1522
return self._wrapped
16-
return None
23+
return None # type: ignore[return-value]

0 commit comments

Comments
 (0)