From 786656a8466f2edb6e5740796a4bf2e2f5aab551 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Fri, 6 Dec 2024 08:47:26 +0100 Subject: [PATCH 1/6] Redefined variables for type security. --- Lib/fontParts/base/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 0b9252cd..4ca0c300 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -7,9 +7,9 @@ class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): from fontTools.ufoLib import fontInfoAttributesVersion3 - copyAttributes = set(fontInfoAttributesVersion3) - copyAttributes.remove("guidelines") - copyAttributes = tuple(copyAttributes) + fontInfoAttributes = set(fontInfoAttributesVersion3) + fontInfoAttributes.remove("guidelines") + copyAttributes = tuple(fontInfoAttributes) def _reprContents(self): contents = [] From 52842803039affefdabf6e3f6b8ebbf2b0cea469 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Sun, 8 Dec 2024 00:51:20 +0100 Subject: [PATCH 2/6] Add type annotations. --- Lib/fontParts/base/info.py | 134 +++++++++++++++++++++++++------------ 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 4ca0c300..0960569d 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -1,17 +1,45 @@ +from __future__ import annotations +from typing import TYPE_CHECKING, Any, Callable, Generic, List, Optional, Tuple, Type, Union + +from fontTools.ufoLib import fontInfoAttributesVersion3 +from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute +from fontMath import MathInfo +from fontMath.mathFunctions import setRoundIntegerFunction + from fontParts.base.base import BaseObject, dynamicProperty, interpolate, reference from fontParts.base import normalizers from fontParts.base.errors import FontPartsError from fontParts.base.deprecated import DeprecatedInfo, RemovedInfo +from fontParts.base.annotations import ( + CharacterMappingType, + CollectionType, + QuadrupleCollectionType, + TransformationType, + ReverseComponentMappingType, + IntFloatType, +) + +if TYPE_CHECKING: + from fontParts.base.font import BaseFont + from fontParts.base.glyph import BaseGlyph + from fontParts.base.lib import BaseLib + + +# Notes +# What are the available types for the `value` parameter in `_validateFontInfoAttributeValue`? +# Where are the magic methods in this class defined? `mypy` throws this error: info.py:107: error: "__hasattr__" undefined in superclass [misc] +# What value types is `BaseInfo.__setattr__` supposed to return? `mypy` throws this error: info.py:113: error: "_getAttr" of "BaseInfo" does not return a value (it only ever returns None) [func-returns-value] +# Isn't `_fromMathInfo` supposed to return anything? It's missing a return statement, yet its called in the return of the public equivalent. class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): - from fontTools.ufoLib import fontInfoAttributesVersion3 + """Represent the basis for an info object.""" fontInfoAttributes = set(fontInfoAttributesVersion3) fontInfoAttributes.remove("guidelines") copyAttributes = tuple(fontInfoAttributes) - def _reprContents(self): + def _reprContents(self) -> List[str]: contents = [] if self.font is not None: contents.append("for font") @@ -24,16 +52,32 @@ def _reprContents(self): # Font - _font = None + _font: Optional[Callable[[], BaseFont]] = None + + font: dynamicProperty = dynamicProperty( + "font", + """Get or set the info's parent font object. + + The value must be a :class:`BaseFont` instance or :obj:`None`. + + :return: The :class:`BaseFont` instance containing the info + or :obj:`None`. + :raises AssertionError: If attempting to set the font when it + has already been set. + + Example:: - font = dynamicProperty("font", "The info's parent font.") + >>> font = info.font - def _get_font(self): + """, + ) + + def _get_font(self) -> Optional[BaseFont]: if self._font is None: return None return self._font() - def _set_font(self, font): + def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: if self._font is not None and self._font != font: raise AssertionError("font for info already set and is not same as font") if font is not None: @@ -45,9 +89,7 @@ def _set_font(self, font): # ---------- @staticmethod - def _validateFontInfoAttributeValue(attr, value): - from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute - + def _validateFontInfoAttributeValue(attr: str, value: Any): valid = validateFontInfoVersion3ValueForAttribute(attr, value) if not valid: raise ValueError(f"Invalid value {value} for attribute '{attr}'.") @@ -59,18 +101,14 @@ def _validateFontInfoAttributeValue(attr, value): # has - def __hasattr__(self, attr): - from fontTools.ufoLib import fontInfoAttributesVersion3 - + def __hasattr__(self, attr: str) -> bool: if attr in fontInfoAttributesVersion3: return True return super(BaseInfo, self).__hasattr__(attr) # get - def __getattribute__(self, attr): - from fontTools.ufoLib import fontInfoAttributesVersion3 - + def __getattribute__(self, attr: str) -> None: if attr != "guidelines" and attr in fontInfoAttributesVersion3: value = self._getAttr(attr) if value is not None: @@ -78,7 +116,7 @@ def __getattribute__(self, attr): return value return super(BaseInfo, self).__getattribute__(attr) - def _getAttr(self, attr): + def _getAttr(self, attr: str) -> None: """ Subclasses may override this method. @@ -86,25 +124,23 @@ def _getAttr(self, attr): it must implement '_get_attributeName' methods for all Info methods. """ - meth = f"_get_{attr}" - if not hasattr(self, meth): + methodName = f"_get_{attr}" + if not hasattr(self, methodName): raise AttributeError(f"No getter for attribute '{attr}'.") - meth = getattr(self, meth) - value = meth() + method = getattr(self, methodName) + value = method() return value # set - def __setattr__(self, attr, value): - from fontTools.ufoLib import fontInfoAttributesVersion3 - + def __setattr__(self, attr: str, value: Any) -> None: if attr != "guidelines" and attr in fontInfoAttributesVersion3: if value is not None: value = self._validateFontInfoAttributeValue(attr, value) return self._setAttr(attr, value) return super(BaseInfo, self).__setattr__(attr, value) - def _setAttr(self, attr, value): + def _setAttr(self, attr: str, value: Any) -> None: """ Subclasses may override this method. @@ -112,17 +148,17 @@ def _setAttr(self, attr, value): it must implement '_set_attributeName' methods for all Info methods. """ - meth = f"_set_{attr}" - if not hasattr(self, meth): + methodName = f"_set_{attr}" + if not hasattr(self, methodName): raise AttributeError(f"No setter for attribute '{attr}'.") - meth = getattr(self, meth) - meth(value) + method = getattr(self, methodName) + method(value) # ------------- # Normalization # ------------- - def round(self): + def round(self) -> None: """ Round the following attributes to integers: @@ -177,12 +213,10 @@ def round(self): """ self._round() - def _round(self, **kwargs): + def _round(self, **kwargs: Any) -> None: """ Subclasses may override this method. """ - from fontMath.mathFunctions import setRoundIntegerFunction - setRoundIntegerFunction(normalizers.normalizeVisualRounding) mathInfo = self._toMathInfo(guidelines=False) @@ -193,14 +227,14 @@ def _round(self, **kwargs): # Updating # -------- - def update(self, other): + def update(self, other: BaseInfo) -> None: """ Update this object with the values from **otherInfo**. """ self._update(other) - def _update(self, other): + def _update(self, other: BaseInfo) -> None: """ Subclasses may override this method. """ @@ -216,7 +250,7 @@ def _update(self, other): # Interpolation # ------------- - def toMathInfo(self, guidelines=True): + def toMathInfo(self, guidelines=True) -> MathInfo: """ Returns the info as an object that follows the `MathGlyph protocol `_. @@ -225,7 +259,7 @@ def toMathInfo(self, guidelines=True): """ return self._toMathInfo(guidelines=guidelines) - def fromMathInfo(self, mathInfo, guidelines=True): + def fromMathInfo(self, mathInfo, guidelines=True) -> BaseInfo: """ Replaces the contents of this info object with the contents of ``mathInfo``. @@ -236,11 +270,10 @@ def fromMathInfo(self, mathInfo, guidelines=True): """ return self._fromMathInfo(mathInfo, guidelines=guidelines) - def _toMathInfo(self, guidelines=True): + def _toMathInfo(self, guidelines=True) -> MathInfo: """ Subclasses may override this method. """ - import fontMath # A little trickery is needed here because MathInfo # handles font level guidelines. Those are not in this @@ -258,11 +291,11 @@ def _toMathInfo(self, guidelines=True): color=guideline.color, ) self.guidelines.append(d) - info = fontMath.MathInfo(self) + info = MathInfo(self) del self.guidelines return info - def _fromMathInfo(self, mathInfo, guidelines=True): + def _fromMathInfo(self, mathInfo, guidelines=True) -> None: """ Subclasses may override this method. """ @@ -278,7 +311,14 @@ def _fromMathInfo(self, mathInfo, guidelines=True): # XXX identifier is lost ) - def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): + def interpolate( + self, + factor: TransformationType, + minInfo: BaseInfo, + maxInfo: BaseInfo, + round: bool = True, + suppressError: bool = True + ) -> None: """ Interpolate all pairs between minInfo and maxInfo. The interpolation occurs on a 0 to 1.0 range where minInfo @@ -310,11 +350,17 @@ def interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): factor, minInfo, maxInfo, round=round, suppressError=suppressError ) - def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True): + def _interpolate( + self, + factor: TransformationType, + minInfo: BaseInfo, + maxInfo: BaseInfo, + round: bool = True, + suppressError: bool = True + ) -> None: """ Subclasses may override this method. """ - from fontMath.mathFunctions import setRoundIntegerFunction setRoundIntegerFunction(normalizers.normalizeVisualRounding) @@ -326,5 +372,5 @@ def _interpolate(self, factor, minInfo, maxInfo, round=True, suppressError=True) f"Info from font '{minInfo.font.name}' and font '{maxInfo.font.name}' could not be interpolated." ) if round: - result = result.round() + result = result.round() # type: ignore[func-returns-value] self._fromMathInfo(result) From 3b78b9e9012e4fc4d58c63506ed8c7605d9c6c41 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Sat, 7 Dec 2024 23:51:45 +0000 Subject: [PATCH 3/6] Format fixes by ruff --- Lib/fontParts/base/info.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 0960569d..e9be1ee8 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -1,5 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Generic, List, Optional, Tuple, Type, Union +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Generic, + List, + Optional, + Tuple, + Type, + Union, +) from fontTools.ufoLib import fontInfoAttributesVersion3 from fontTools.ufoLib import validateFontInfoVersion3ValueForAttribute @@ -32,6 +42,7 @@ # What value types is `BaseInfo.__setattr__` supposed to return? `mypy` throws this error: info.py:113: error: "_getAttr" of "BaseInfo" does not return a value (it only ever returns None) [func-returns-value] # Isn't `_fromMathInfo` supposed to return anything? It's missing a return statement, yet its called in the return of the public equivalent. + class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object.""" @@ -77,7 +88,9 @@ def _get_font(self) -> Optional[BaseFont]: return None return self._font() - def _set_font(self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]]) -> None: + def _set_font( + self, font: Optional[Union[BaseFont, Callable[[], BaseFont]]] + ) -> None: if self._font is not None and self._font != font: raise AssertionError("font for info already set and is not same as font") if font is not None: @@ -317,7 +330,7 @@ def interpolate( minInfo: BaseInfo, maxInfo: BaseInfo, round: bool = True, - suppressError: bool = True + suppressError: bool = True, ) -> None: """ Interpolate all pairs between minInfo and maxInfo. @@ -356,7 +369,7 @@ def _interpolate( minInfo: BaseInfo, maxInfo: BaseInfo, round: bool = True, - suppressError: bool = True + suppressError: bool = True, ) -> None: """ Subclasses may override this method. From 64365af1ad5ceddc3012b832322388ae268c8af1 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Sun, 8 Dec 2024 01:14:13 +0100 Subject: [PATCH 4/6] Delete notes. --- Lib/fontParts/base/info.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 0960569d..fb349e69 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -27,11 +27,6 @@ # Notes -# What are the available types for the `value` parameter in `_validateFontInfoAttributeValue`? -# Where are the magic methods in this class defined? `mypy` throws this error: info.py:107: error: "__hasattr__" undefined in superclass [misc] -# What value types is `BaseInfo.__setattr__` supposed to return? `mypy` throws this error: info.py:113: error: "_getAttr" of "BaseInfo" does not return a value (it only ever returns None) [func-returns-value] -# Isn't `_fromMathInfo` supposed to return anything? It's missing a return statement, yet its called in the return of the public equivalent. - class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object.""" From 524abfa4af16dd17670dfd924fde0e74e60055ea Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Sun, 8 Dec 2024 01:24:14 +0100 Subject: [PATCH 5/6] Remove unused imports. --- Lib/fontParts/base/info.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 02b532c0..5a7ea754 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -3,11 +3,8 @@ TYPE_CHECKING, Any, Callable, - Generic, List, Optional, - Tuple, - Type, Union, ) @@ -21,23 +18,15 @@ from fontParts.base.errors import FontPartsError from fontParts.base.deprecated import DeprecatedInfo, RemovedInfo from fontParts.base.annotations import ( - CharacterMappingType, - CollectionType, - QuadrupleCollectionType, TransformationType, - ReverseComponentMappingType, - IntFloatType, ) if TYPE_CHECKING: from fontParts.base.font import BaseFont - from fontParts.base.glyph import BaseGlyph - from fontParts.base.lib import BaseLib # Notes - class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object.""" From 0356b3eb57724d1f19560ff0c5cd9a5142949ce6 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Sun, 8 Dec 2024 00:24:43 +0000 Subject: [PATCH 6/6] Format fixes by ruff --- Lib/fontParts/base/info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/fontParts/base/info.py b/Lib/fontParts/base/info.py index 5a7ea754..1978143f 100644 --- a/Lib/fontParts/base/info.py +++ b/Lib/fontParts/base/info.py @@ -27,6 +27,7 @@ # Notes + class BaseInfo(BaseObject, DeprecatedInfo, RemovedInfo): """Represent the basis for an info object."""