From d977313bf7473015fb510831e3b2df410b580212 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Fri, 29 Nov 2024 13:55:11 +0100 Subject: [PATCH 01/14] Resolve `mypy` errors. --- Lib/fontParts/base/bPoint.py | 1 + Lib/fontParts/base/contour.py | 7 ++++--- Lib/fontParts/base/font.py | 6 +++--- Lib/fontParts/base/glyph.py | 7 ++++--- Lib/fontParts/base/layer.py | 9 +++++---- Lib/fontParts/base/segment.py | 8 +++++--- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Lib/fontParts/base/bPoint.py b/Lib/fontParts/base/bPoint.py index c4c8b862..31f36ec0 100644 --- a/Lib/fontParts/base/bPoint.py +++ b/Lib/fontParts/base/bPoint.py @@ -17,6 +17,7 @@ PairType, PairCollectionType, SextupleCollectionType, + IntFloatType ) if TYPE_CHECKING: diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index 7fd45b3c..53361de5 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -476,14 +476,15 @@ def _transformBy( compatibilityReporterClass = ContourCompatibilityReporter - def isCompatible(self, other: BaseContour) -> Tuple[bool, str]: + def isCompatible(self, other: BaseContour) -> Tuple[bool, ContourCompatibilityReporter]: """Evaluate interpolation compatibility with another contour. :param other: The other :class:`BaseContour` instance to check compatibility with. :return: A :class:`tuple` where the first element is a :class:`bool` - indicating compatibility, and the second element is a :class:`str` - of compatibility notes. + indicating compatibility, and the second element is + a :class:`fontParts.base.compatibility.ContourCompatibilityReporter` + instance. Example:: diff --git a/Lib/fontParts/base/font.py b/Lib/fontParts/base/font.py index 3762eaea..620d1513 100644 --- a/Lib/fontParts/base/font.py +++ b/Lib/fontParts/base/font.py @@ -2058,7 +2058,7 @@ def _interpolate( compatibilityReporterClass = FontCompatibilityReporter - def isCompatible(self, other: BaseFont) -> Tuple[bool, str]: + def isCompatible(self, other: BaseFont) -> Tuple[bool, FontCompatibilityReporter]: """Evaluate interpolation compatibility with another font. This method will return a :class:`bool` indicating if the font is @@ -2068,8 +2068,8 @@ def isCompatible(self, other: BaseFont) -> Tuple[bool, str]: :param other: The other :class:`BaseFont` instance to check compatibility with. :return: A :class:`tuple` where the first element is a :class:`bool` - indicating compatibility, and the second element is a :class:`str` - of compatibility notes. + indicating compatibility, and the second element is + a :class:`fontParts.base.compatibility.FontCompatibilityReporter` instance. Example:: diff --git a/Lib/fontParts/base/glyph.py b/Lib/fontParts/base/glyph.py index d7e0be57..13b02a7a 100644 --- a/Lib/fontParts/base/glyph.py +++ b/Lib/fontParts/base/glyph.py @@ -2795,14 +2795,15 @@ def _checkPairs( reporter.warning = True reporterObject.append(compatibility) - def isCompatible(self, other: BaseGlyph) -> Tuple[bool, str]: + def isCompatible(self, other: BaseGlyph) -> Tuple[bool, GlyphCompatibilityReporter]: """Evaluate interpolation compatibility with another glyph. :param other: The other :class:`BaseGlyph` instance to check compatibility with. :return: A :class:`tuple` where the first element is a :class:`bool` - indicating compatibility, and the second element is a :class:`str` - of compatibility notes. + indicating compatibility, and the second element is + a :class:`fontParts.base.compatibility.GlyphCompatibilityReporter` + instance. """ return super(BaseGlyph, self).isCompatible(other, BaseGlyph) diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index 62fffb3d..93703ef5 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1075,14 +1075,15 @@ def _interpolate( compatibilityReporterClass = LayerCompatibilityReporter - def isCompatible(self, other: BaseLayer) -> tuple[bool, str]: + def isCompatible(self, other: BaseLayer) -> Tuple[bool, LayerCompatibilityReporter]: """Evaluate interpolation compatibility with another layer. :param other: The other :class:`BaseLayer` instance to check compatibility with. :return: A :class:`tuple` where the first element is a :class:`bool` - indicating compatibility, and the second element is a :class:`str` - of compatibility notes. + indicating compatibility, and the second element is + a :class:`fontParts.base.compatibility.LayerCompatibilityReporter` + instance. Example:: @@ -1098,7 +1099,7 @@ def isCompatible(self, other: BaseLayer) -> tuple[bool, str]: return super(BaseLayer, self).isCompatible(other, BaseLayer) def _isCompatible( - self, other: BaseLib, reporter: LayerCompatibilityReporter + self, other: BaseLayer, reporter: LayerCompatibilityReporter ) -> None: """Evaluate interpolation compatibility with another native layer. diff --git a/Lib/fontParts/base/segment.py b/Lib/fontParts/base/segment.py index 62e9b65e..1892afd5 100644 --- a/Lib/fontParts/base/segment.py +++ b/Lib/fontParts/base/segment.py @@ -613,7 +613,8 @@ def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: A compatibilityReporterClass = SegmentCompatibilityReporter - def isCompatible(self, other: BaseSegment) -> Tuple[bool, str]: + def isCompatible(self, + other: BaseSegment) -> Tuple[bool, SegmentCompatibilityReporter]: """Evaluate interpolation compatibility with another segment. This method will return a :class:`bool` indicating if the segment is @@ -623,8 +624,9 @@ def isCompatible(self, other: BaseSegment) -> Tuple[bool, str]: :param other: The other :class:`BaseSegment` instance to check compatibility with. :return: A :class:`tuple` where the first element is a :class:`bool` - indicating compatibility, and the second element is a :class:`str` - of compatibility notes. + indicating compatibility, and the second element is + a :class:`fontParts.base.compatibility.SegmentCompatibilityReporter` + instance. Example:: From 2036f8363354b9f2f34d429b162eb413c06f49b6 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Fri, 29 Nov 2024 12:55:45 +0000 Subject: [PATCH 02/14] Format fixes by ruff --- Lib/fontParts/base/bPoint.py | 2 +- Lib/fontParts/base/contour.py | 4 +++- Lib/fontParts/base/segment.py | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/fontParts/base/bPoint.py b/Lib/fontParts/base/bPoint.py index 31f36ec0..0fa173e5 100644 --- a/Lib/fontParts/base/bPoint.py +++ b/Lib/fontParts/base/bPoint.py @@ -17,7 +17,7 @@ PairType, PairCollectionType, SextupleCollectionType, - IntFloatType + IntFloatType, ) if TYPE_CHECKING: diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index 53361de5..c1a644f2 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -476,7 +476,9 @@ def _transformBy( compatibilityReporterClass = ContourCompatibilityReporter - def isCompatible(self, other: BaseContour) -> Tuple[bool, ContourCompatibilityReporter]: + def isCompatible( + self, other: BaseContour + ) -> Tuple[bool, ContourCompatibilityReporter]: """Evaluate interpolation compatibility with another contour. :param other: The other :class:`BaseContour` instance to check diff --git a/Lib/fontParts/base/segment.py b/Lib/fontParts/base/segment.py index 1892afd5..d0267513 100644 --- a/Lib/fontParts/base/segment.py +++ b/Lib/fontParts/base/segment.py @@ -613,8 +613,9 @@ def _transformBy(self, matrix: SextupleCollectionType[IntFloatType], **kwargs: A compatibilityReporterClass = SegmentCompatibilityReporter - def isCompatible(self, - other: BaseSegment) -> Tuple[bool, SegmentCompatibilityReporter]: + def isCompatible( + self, other: BaseSegment + ) -> Tuple[bool, SegmentCompatibilityReporter]: """Evaluate interpolation compatibility with another segment. This method will return a :class:`bool` indicating if the segment is From af609309a78c5db1af8977eae77f8d1472a01fcf Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Wed, 4 Dec 2024 07:55:47 +0100 Subject: [PATCH 03/14] Improve annotation. - Add generic generic type arguments to view objects and `BaseDict`. - Solve conflicting typing in `lib.py`. - Adjust documentation. --- Lib/fontParts/base/base.py | 94 ++++++++++++++++++++------------------ Lib/fontParts/base/lib.py | 4 +- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 56efa93c..7aee5945 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -4,7 +4,7 @@ TYPE_CHECKING, Any, Callable, - Dict, + Generic, Iterable, Iterator, List, @@ -36,6 +36,8 @@ from collections.abc import ItemsView BaseObjectType = TypeVar("BaseObjectType", bound="BaseObject") +KeyType = TypeVar("KeyType") +ValueType = TypeVar("ValueType") # ------- # Helpers @@ -394,7 +396,7 @@ def naked(self) -> Any: self.raiseNotImplementedError() -class BaseItems: +class BaseItems(Generic[KeyType, ValueType]): """Provide the given mapping with an items view object. This class provides a view of the key-value pairs in a mapping, similar to @@ -410,10 +412,10 @@ class BaseItems: """ - def __init__(self, mapping: BaseDict) -> None: + def __init__(self, mapping: BaseDict[KeyType, ValueType]) -> None: self._mapping = mapping - def __contains__(self, item: Tuple[str, Any]) -> bool: + def __contains__(self, item: Tuple[KeyType, ValueType]) -> bool: """Check if a key-value pair exists in the mapping. :param item: The key-value pair to check for existence as a :class:`tuple`. @@ -427,7 +429,7 @@ def __contains__(self, item: Tuple[str, Any]) -> bool: ) return normalizedItem in self._mapping._normalizeItems() - def __iter__(self) -> Iterator[Tuple[str, Any]]: + def __iter__(self) -> Iterator[Tuple[KeyType, ValueType]]: """Return an iterator over the key-value pairs in the mapping. This method yields each item one by one, removing it from the list of @@ -455,7 +457,7 @@ def __repr__(self) -> str: return f"{self._mapping.__class__.__name__}_items({list(self)})" -class BaseKeys: +class BaseKeys(Generic[KeyType]): """Provide the given mapping with a keys view object. This class provides a view of the keys in a mapping, similar to the behavior @@ -470,10 +472,10 @@ class BaseKeys: """ - def __init__(self, mapping: BaseDict) -> None: + def __init__(self, mapping: BaseDict[KeyType, Any]) -> None: self._mapping = mapping - def __contains__(self, key: Any) -> bool: + def __contains__(self, key: KeyType) -> bool: """Check if a key exists in the mapping. :param key: The key to check for existence. @@ -482,7 +484,7 @@ def __contains__(self, key: Any) -> bool: """ return any(k == key for k, _ in self._mapping._normalizeItems()) - def __iter__(self) -> Iterator[Any]: + def __iter__(self) -> Iterator[KeyType]: """Return an iterator over the keys in the mapping. This method yields each key one by one, removing it from the list of @@ -509,7 +511,7 @@ def __repr__(self) -> str: """ return f"{self._mapping.__class__.__name__}_keys({list(self)})" - def isdisjoint(self, other: Iterable[Any]) -> bool: + def isdisjoint(self, other: Iterable[KeyType]) -> bool: """Check if the keys view has no common elements with another iterable. :param other: The iterable to compare against. @@ -519,7 +521,7 @@ def isdisjoint(self, other: Iterable[Any]) -> bool: return set(self).isdisjoint(other) -class BaseValues: +class BaseValues(Generic[ValueType]): """Provide the given mapping with a values view object. This class provides a view of the values in a mapping, similar to the behavior @@ -534,10 +536,10 @@ class BaseValues: """ - def __init__(self, mapping: BaseDict) -> None: + def __init__(self, mapping: BaseDict[Any, ValueType]) -> None: self._mapping = mapping - def __contains__(self, value: Any) -> bool: + def __contains__(self, value: ValueType) -> bool: """Check if a value exists in the mapping. :param value: The value to check for existence. @@ -546,7 +548,7 @@ def __contains__(self, value: Any) -> bool: """ return any(v == value for _, v in self._mapping._normalizeItems()) - def __iter__(self) -> Iterator[Any]: + def __iter__(self) -> Iterator[ValueType]: """Return an iterator over the values in the mapping. This method yields each value one by one, removing it from the list of @@ -574,7 +576,7 @@ def __repr__(self) -> str: return f"{self._mapping.__class__.__name__}_values({list(self)})" -class BaseDict(BaseObject): +class BaseDict(BaseObject, Generic[KeyType, ValueType]): """Provide objects with basic dictionary-like functionality. :cvar keyNormalizer: An optional normalizer function for keys. @@ -582,18 +584,18 @@ class BaseDict(BaseObject): """ - keyNormalizer: Optional[Callable[[str], str]] = None - valueNormalizer: Optional[Callable[[Any], Any]] = None + keyNormalizer: Optional[Callable[[KeyType], KeyType]] = None + valueNormalizer: Optional[Callable[[ValueType], ValueType]] = None - def _normalizeKey(self, key: str) -> str: + def _normalizeKey(self, key: KeyType) -> KeyType: keyNormalizer = type(self).keyNormalizer return keyNormalizer(key) if keyNormalizer is not None else key - def _normalizeValue(self, value: Any) -> Any: + def _normalizeValue(self, value: ValueType) -> ValueType: valueNormalizer = type(self).valueNormalizer return valueNormalizer(value) if valueNormalizer is not None else value - def _normalizeItems(self) -> List[Tuple[str, Any]]: + def _normalizeItems(self) -> List[Tuple[KeyType, ValueType]]: items = self._items() return [(self._normalizeKey(k), self._normalizeValue(v)) for (k, v) in items] @@ -635,20 +637,20 @@ def _len(self) -> int: """ return len(self.keys()) - def keys(self) -> BaseKeys: + def keys(self) -> BaseKeys[KeyType]: """Return a view of the keys in the object. - :return: A :class:`BaseKeys` object instance. + :return: A :class:`BaseKeys` object instance of :class:`str` items. """ return self._keys() - def _keys(self) -> BaseKeys: + def _keys(self) -> BaseKeys[KeyType]: """Return a view of the keys in the native object. This is the environment implementation of :meth:`BaseDict.keys`. - :return: A :class:`BaseKeys` object instance. If + :return: A :class:`BaseKeys` object instance of :class:`str` items. If a :cvar:`BaseDict.keyNormalizer` is set, it will be applied to each key in the returned view. @@ -667,7 +669,7 @@ def items(self) -> BaseItems: """ return BaseItems(self) - def _items(self) -> ItemsView: + def _items(self) -> ItemsView[KeyType, ValueType]: """Return a view of the key-value pairs in the native object. This is the environment implementation of :meth:`BaseDict.items`. @@ -685,7 +687,7 @@ def _items(self) -> ItemsView: """ self.raiseNotImplementedError() - def values(self) -> BaseValues: + def values(self) -> BaseValues[ValueType]: """Return a view of the values in the object. :return: A :class:`BaseValues` object instance. @@ -693,7 +695,7 @@ def values(self) -> BaseValues: """ return self._values() - def _values(self) -> BaseValues: + def _values(self) -> BaseValues[ValueType]: """Return a view of the values in the native object. This is the environment implementation of :meth:`BaseDict.values`. @@ -709,7 +711,7 @@ def _values(self) -> BaseValues: """ return BaseValues(self) - def __contains__(self, key: Any) -> bool: + def __contains__(self, key: KeyType) -> bool: """Check if a key is in the object. :param key: The key to check for. @@ -719,7 +721,7 @@ def __contains__(self, key: Any) -> bool: key = self._normalizeKey(key) return self._contains(key) - def _contains(self, key: Any) -> bool: + def _contains(self, key: KeyType) -> bool: """Check if a key is in the native object. This is the environment implementation of :meth:`BaseDict.__contains__`. @@ -738,7 +740,7 @@ def _contains(self, key: Any) -> bool: has_key = __contains__ - def __setitem__(self, key: Any, value: Any) -> None: + def __setitem__(self, key: KeyType, value: ValueType) -> None: """Set the value for a given key in the object. :param key: The key to set. @@ -749,7 +751,7 @@ def __setitem__(self, key: Any, value: Any) -> None: value = self._normalizeValue(value) self._setItem(key, value) - def _setItem(self, key: Any, value: Any) -> None: + def _setItem(self, key: KeyType, value: ValueType) -> None: """Set the value for a given key in the native object. This is the environment implementation of :meth:`BaseDict.__setitem__`. @@ -769,7 +771,7 @@ def _setItem(self, key: Any, value: Any) -> None: """ self.raiseNotImplementedError() - def __getitem__(self, key: Any) -> Any: + def __getitem__(self, key: KeyType) -> ValueType: """Get the value for a given key from the object. :param key: The key to retrieve the value for. @@ -780,7 +782,7 @@ def __getitem__(self, key: Any) -> Any: value = self._getItem(key) return self._normalizeValue(value) - def _getItem(self, key: Any) -> Any: + def _getItem(self, key: KeyType) -> ValueType: """Get the value for a given key from the native object. This is the environment implementation of :meth:`BaseDict.__getitem__`. @@ -801,7 +803,7 @@ def _getItem(self, key: Any) -> Any: """ self.raiseNotImplementedError() - def get(self, key: Any, default: Optional[Any] = None) -> Any: + def get(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[ValueType]: """Get the value for a given key in the object. If the given key is not found, The specified `default` will be returned. @@ -816,11 +818,11 @@ def get(self, key: Any, default: Optional[Any] = None) -> Any: key = self._normalizeKey(key) default = self._normalizeValue(default) if default is not None else default value = self._get(key, default=default) - if value is not default: + if value is not None and value is not default: value = self._normalizeValue(value) return value - def _get(self, key: Any, default: Optional[Any]) -> Any: + def _get(self, key: KeyType, default: Optional[ValueType]) -> Optional[ValueType]: """Get the value for a given key in the native object. This is the environment implementation of :meth:`BaseDict.get`. @@ -841,7 +843,7 @@ def _get(self, key: Any, default: Optional[Any]) -> Any: return self[key] return default - def __delitem__(self, key: Any) -> None: + def __delitem__(self, key: KeyType) -> None: """Delete a key-value pair from the object. :param key: The key to delete. @@ -850,7 +852,7 @@ def __delitem__(self, key: Any) -> None: key = self._normalizeKey(key) self._delItem(key) - def _delItem(self, key: Any) -> None: + def _delItem(self, key: KeyType) -> None: """Delete a key-value pair from the native object. This is the environment implementation of :meth:`BaseDict.__delitem__`. @@ -867,7 +869,7 @@ def _delItem(self, key: Any) -> None: """ self.raiseNotImplementedError() - def pop(self, key: Any, default: Optional[Any] = None) -> Any: + def pop(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[ValueType]: """Remove a key from the object and return it's value. If the given key is not found, The specified `default` will be returned. @@ -883,9 +885,11 @@ def pop(self, key: Any, default: Optional[Any] = None) -> Any: if default is not None: default = self._normalizeValue(default) value = self._pop(key, default=default) - return self._normalizeValue(value) + if value is not None: + value = self._normalizeValue(value) + return value - def _pop(self, key: Any, default: Optional[Any]) -> Any: + def _pop(self, key: KeyType, default: Optional[ValueType]) -> Optional[ValueType]: """Remove a key from the native object and return it's value. This is the environment implementation of :meth:`BaseDict.pop`. @@ -908,7 +912,7 @@ def _pop(self, key: Any, default: Optional[Any]) -> Any: del self[key] return value - def __iter__(self) -> Iterator[Any]: + def __iter__(self) -> Iterator[KeyType]: """Return an iterator over the keys of the object. This method yields each key one by one, removing it from the list of @@ -919,7 +923,7 @@ def __iter__(self) -> Iterator[Any]: """ return self._iter() - def _iter(self) -> Iterator[Any]: + def _iter(self) -> Iterator[KeyType]: """Return an iterator over the keys of the native object. This is the environment implementation of :meth:`BaseDict.__iter__`. @@ -934,7 +938,7 @@ def _iter(self) -> Iterator[Any]: for key in self.keys(): yield key - def update(self, other: MutableMapping) -> None: + def update(self, other: MutableMapping[KeyType, ValueType]) -> None: """Update the current object instance with key-value pairs from another. :param other: A :class:`MutableMapping` of key-value pairs to update @@ -950,7 +954,7 @@ def update(self, other: MutableMapping) -> None: otherCopy = d self._update(otherCopy) - def _update(self, other: MutableMapping) -> None: + def _update(self, other: MutableMapping[KeyType, ValueType]) -> None: """Update the current native object instance with key-value pairs from another. This is the environment implementation of :meth:`BaseDict.update`. diff --git a/Lib/fontParts/base/lib.py b/Lib/fontParts/base/lib.py index 6329bfb4..544b3299 100644 --- a/Lib/fontParts/base/lib.py +++ b/Lib/fontParts/base/lib.py @@ -310,7 +310,7 @@ def clear(self) -> None: """ super(BaseLib, self).clear() - def get(self, key: str, default: Optional[LibValueType] = None) -> LibValueType: + def get(self, key: str, default: Optional[LibValueType] = None) -> Optional[LibValueType]: """Get the value for the given key in the lib. If the given `key` is not found, The specified `default` will be returned. @@ -386,7 +386,7 @@ def values(self) -> BaseValues[LibValueType]: """ return super(BaseLib, self).values() - def pop(self, key: str, default: Optional[LibValueType] = None) -> LibValueType: + def pop(self, key: str, default: Optional[LibValueType] = None) -> Optional[LibValueType]: """Remove the specified key and return its associated value. If the `key` does not exist, the `default` value is returned. From e2edfa9c65933396e611f183bcee86c3fb1bd62f Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Wed, 4 Dec 2024 10:27:05 +0100 Subject: [PATCH 04/14] Add abstract members. --- Lib/fontParts/base/base.py | 67 ++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 7aee5945..c20dae7c 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1,5 +1,6 @@ # pylint: disable=C0103, C0114 from __future__ import annotations +from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, Any, @@ -15,9 +16,9 @@ TypeVar, Union, ) +from collections.abc import MutableMapping from copy import deepcopy import math -from collections.abc import MutableMapping from fontTools.misc import transform from fontParts.base.errors import FontPartsError @@ -990,7 +991,7 @@ def _clear(self) -> None: del self[key] -class TransformationMixin: +class TransformationMixin(ABC): """Provide objects transformation-related functionality.""" # --------------- @@ -1248,8 +1249,16 @@ def _skewBy( t = transform.Identity.skew(x=x, y=y) self.transformBy(tuple(t), origin=origin, **kwargs) + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + -class InterpolationMixin: +class InterpolationMixin(ABC): """Provide objects with interpolation-related functionality. :cvar compatibilityReporterClass: A class used for reporting interpolation @@ -1303,8 +1312,16 @@ def _isCompatible(self, other: Any, reporter: Any) -> None: """ self.raiseNotImplementedError() + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + -class SelectionMixin: +class SelectionMixin(ABC): """Provide objects with selection-related functionality.""" # ------------- @@ -1389,8 +1406,16 @@ def _setSelectedSubObjects( for obj in subObjects: obj.selected = obj in selected + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + -class PointPositionMixin: +class PointPositionMixin(ABC): """Provide objects with the ability to determine point position. This class adds a `position` attribute as a :class:`dyanmicProperty`, for @@ -1456,8 +1481,30 @@ def _set_position(self, value: PairCollectionType[IntFloatType]) -> None: dY = y - pY self.moveBy((dX, dY)) + # ---------------- + # Abstract members + # ---------------- -class IdentifierMixin: + @abstractmethod + @property + def x(self): + pass + + @abstractmethod + @property + def y(self): + pass + + @abstractmethod + def moveBy(self, value): + pass + + @abstractmethod + def raiseNotImplementedError(self): + pass + + +class IdentifierMixin(ABC): """Provide objects with a unique identifier.""" # identifier @@ -1548,6 +1595,14 @@ def _setIdentifier(self, value: str) -> None: """ pass + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + def reference(obj: Callable[[], Any]) -> Callable[[], Any]: """ From 866d5dd8abb9875d93d12a6734d98e5cbf767ccf Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Wed, 4 Dec 2024 09:27:49 +0000 Subject: [PATCH 05/14] Format fixes by ruff --- Lib/fontParts/base/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index c20dae7c..626de635 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -804,7 +804,9 @@ def _getItem(self, key: KeyType) -> ValueType: """ self.raiseNotImplementedError() - def get(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[ValueType]: + def get( + self, key: KeyType, default: Optional[ValueType] = None + ) -> Optional[ValueType]: """Get the value for a given key in the object. If the given key is not found, The specified `default` will be returned. @@ -870,7 +872,9 @@ def _delItem(self, key: KeyType) -> None: """ self.raiseNotImplementedError() - def pop(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[ValueType]: + def pop( + self, key: KeyType, default: Optional[ValueType] = None + ) -> Optional[ValueType]: """Remove a key from the object and return it's value. If the given key is not found, The specified `default` will be returned. From 3b0f4cb8800ac636107cbf097d0bc38addc9a68d Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Wed, 4 Dec 2024 12:47:23 +0100 Subject: [PATCH 06/14] Revert "Add abstract members." This reverts commit e2edfa9c65933396e611f183bcee86c3fb1bd62f. --- Lib/fontParts/base/base.py | 67 ++++---------------------------------- 1 file changed, 6 insertions(+), 61 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index c20dae7c..7aee5945 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1,6 +1,5 @@ # pylint: disable=C0103, C0114 from __future__ import annotations -from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, Any, @@ -16,9 +15,9 @@ TypeVar, Union, ) -from collections.abc import MutableMapping from copy import deepcopy import math +from collections.abc import MutableMapping from fontTools.misc import transform from fontParts.base.errors import FontPartsError @@ -991,7 +990,7 @@ def _clear(self) -> None: del self[key] -class TransformationMixin(ABC): +class TransformationMixin: """Provide objects transformation-related functionality.""" # --------------- @@ -1249,16 +1248,8 @@ def _skewBy( t = transform.Identity.skew(x=x, y=y) self.transformBy(tuple(t), origin=origin, **kwargs) - # ---------------- - # Abstract members - # ---------------- - - @abstractmethod - def raiseNotImplementedError(self): - pass - -class InterpolationMixin(ABC): +class InterpolationMixin: """Provide objects with interpolation-related functionality. :cvar compatibilityReporterClass: A class used for reporting interpolation @@ -1312,16 +1303,8 @@ def _isCompatible(self, other: Any, reporter: Any) -> None: """ self.raiseNotImplementedError() - # ---------------- - # Abstract members - # ---------------- - - @abstractmethod - def raiseNotImplementedError(self): - pass - -class SelectionMixin(ABC): +class SelectionMixin: """Provide objects with selection-related functionality.""" # ------------- @@ -1406,16 +1389,8 @@ def _setSelectedSubObjects( for obj in subObjects: obj.selected = obj in selected - # ---------------- - # Abstract members - # ---------------- - - @abstractmethod - def raiseNotImplementedError(self): - pass - -class PointPositionMixin(ABC): +class PointPositionMixin: """Provide objects with the ability to determine point position. This class adds a `position` attribute as a :class:`dyanmicProperty`, for @@ -1481,30 +1456,8 @@ def _set_position(self, value: PairCollectionType[IntFloatType]) -> None: dY = y - pY self.moveBy((dX, dY)) - # ---------------- - # Abstract members - # ---------------- - @abstractmethod - @property - def x(self): - pass - - @abstractmethod - @property - def y(self): - pass - - @abstractmethod - def moveBy(self, value): - pass - - @abstractmethod - def raiseNotImplementedError(self): - pass - - -class IdentifierMixin(ABC): +class IdentifierMixin: """Provide objects with a unique identifier.""" # identifier @@ -1595,14 +1548,6 @@ def _setIdentifier(self, value: str) -> None: """ pass - # ---------------- - # Abstract members - # ---------------- - - @abstractmethod - def raiseNotImplementedError(self): - pass - def reference(obj: Callable[[], Any]) -> Callable[[], Any]: """ From e2d10d65398cc8dc0a5abf31dca61174c9453434 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Wed, 4 Dec 2024 12:48:23 +0100 Subject: [PATCH 07/14] Add abstract members. --- Lib/fontParts/base/base.py | 159 ++++++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 54 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 7aee5945..6ee5b294 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1,10 +1,11 @@ # pylint: disable=C0103, C0114 from __future__ import annotations +from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, Any, Callable, - Generic, + Dict, Iterable, Iterator, List, @@ -36,8 +37,6 @@ from collections.abc import ItemsView BaseObjectType = TypeVar("BaseObjectType", bound="BaseObject") -KeyType = TypeVar("KeyType") -ValueType = TypeVar("ValueType") # ------- # Helpers @@ -396,7 +395,7 @@ def naked(self) -> Any: self.raiseNotImplementedError() -class BaseItems(Generic[KeyType, ValueType]): +class BaseItems: """Provide the given mapping with an items view object. This class provides a view of the key-value pairs in a mapping, similar to @@ -412,10 +411,10 @@ class BaseItems(Generic[KeyType, ValueType]): """ - def __init__(self, mapping: BaseDict[KeyType, ValueType]) -> None: + def __init__(self, mapping: BaseDict) -> None: self._mapping = mapping - def __contains__(self, item: Tuple[KeyType, ValueType]) -> bool: + def __contains__(self, item: Tuple[str, Any]) -> bool: """Check if a key-value pair exists in the mapping. :param item: The key-value pair to check for existence as a :class:`tuple`. @@ -429,7 +428,7 @@ def __contains__(self, item: Tuple[KeyType, ValueType]) -> bool: ) return normalizedItem in self._mapping._normalizeItems() - def __iter__(self) -> Iterator[Tuple[KeyType, ValueType]]: + def __iter__(self) -> Iterator[Tuple[str, Any]]: """Return an iterator over the key-value pairs in the mapping. This method yields each item one by one, removing it from the list of @@ -457,7 +456,7 @@ def __repr__(self) -> str: return f"{self._mapping.__class__.__name__}_items({list(self)})" -class BaseKeys(Generic[KeyType]): +class BaseKeys: """Provide the given mapping with a keys view object. This class provides a view of the keys in a mapping, similar to the behavior @@ -472,10 +471,10 @@ class BaseKeys(Generic[KeyType]): """ - def __init__(self, mapping: BaseDict[KeyType, Any]) -> None: + def __init__(self, mapping: BaseDict) -> None: self._mapping = mapping - def __contains__(self, key: KeyType) -> bool: + def __contains__(self, key: Any) -> bool: """Check if a key exists in the mapping. :param key: The key to check for existence. @@ -484,7 +483,7 @@ def __contains__(self, key: KeyType) -> bool: """ return any(k == key for k, _ in self._mapping._normalizeItems()) - def __iter__(self) -> Iterator[KeyType]: + def __iter__(self) -> Iterator[Any]: """Return an iterator over the keys in the mapping. This method yields each key one by one, removing it from the list of @@ -511,7 +510,7 @@ def __repr__(self) -> str: """ return f"{self._mapping.__class__.__name__}_keys({list(self)})" - def isdisjoint(self, other: Iterable[KeyType]) -> bool: + def isdisjoint(self, other: Iterable[Any]) -> bool: """Check if the keys view has no common elements with another iterable. :param other: The iterable to compare against. @@ -521,7 +520,7 @@ def isdisjoint(self, other: Iterable[KeyType]) -> bool: return set(self).isdisjoint(other) -class BaseValues(Generic[ValueType]): +class BaseValues: """Provide the given mapping with a values view object. This class provides a view of the values in a mapping, similar to the behavior @@ -536,10 +535,10 @@ class BaseValues(Generic[ValueType]): """ - def __init__(self, mapping: BaseDict[Any, ValueType]) -> None: + def __init__(self, mapping: BaseDict) -> None: self._mapping = mapping - def __contains__(self, value: ValueType) -> bool: + def __contains__(self, value: Any) -> bool: """Check if a value exists in the mapping. :param value: The value to check for existence. @@ -548,7 +547,7 @@ def __contains__(self, value: ValueType) -> bool: """ return any(v == value for _, v in self._mapping._normalizeItems()) - def __iter__(self) -> Iterator[ValueType]: + def __iter__(self) -> Iterator[Any]: """Return an iterator over the values in the mapping. This method yields each value one by one, removing it from the list of @@ -576,7 +575,7 @@ def __repr__(self) -> str: return f"{self._mapping.__class__.__name__}_values({list(self)})" -class BaseDict(BaseObject, Generic[KeyType, ValueType]): +class BaseDict(BaseObject): """Provide objects with basic dictionary-like functionality. :cvar keyNormalizer: An optional normalizer function for keys. @@ -584,18 +583,18 @@ class BaseDict(BaseObject, Generic[KeyType, ValueType]): """ - keyNormalizer: Optional[Callable[[KeyType], KeyType]] = None - valueNormalizer: Optional[Callable[[ValueType], ValueType]] = None + keyNormalizer: Optional[Callable[[str], str]] = None + valueNormalizer: Optional[Callable[[Any], Any]] = None - def _normalizeKey(self, key: KeyType) -> KeyType: + def _normalizeKey(self, key: str) -> str: keyNormalizer = type(self).keyNormalizer return keyNormalizer(key) if keyNormalizer is not None else key - def _normalizeValue(self, value: ValueType) -> ValueType: + def _normalizeValue(self, value: Any) -> Any: valueNormalizer = type(self).valueNormalizer return valueNormalizer(value) if valueNormalizer is not None else value - def _normalizeItems(self) -> List[Tuple[KeyType, ValueType]]: + def _normalizeItems(self) -> List[Tuple[str, Any]]: items = self._items() return [(self._normalizeKey(k), self._normalizeValue(v)) for (k, v) in items] @@ -637,20 +636,20 @@ def _len(self) -> int: """ return len(self.keys()) - def keys(self) -> BaseKeys[KeyType]: + def keys(self) -> BaseKeys: """Return a view of the keys in the object. - :return: A :class:`BaseKeys` object instance of :class:`str` items. + :return: A :class:`BaseKeys` object instance. """ return self._keys() - def _keys(self) -> BaseKeys[KeyType]: + def _keys(self) -> BaseKeys: """Return a view of the keys in the native object. This is the environment implementation of :meth:`BaseDict.keys`. - :return: A :class:`BaseKeys` object instance of :class:`str` items. If + :return: A :class:`BaseKeys` object instance. If a :cvar:`BaseDict.keyNormalizer` is set, it will be applied to each key in the returned view. @@ -669,7 +668,7 @@ def items(self) -> BaseItems: """ return BaseItems(self) - def _items(self) -> ItemsView[KeyType, ValueType]: + def _items(self) -> ItemsView: """Return a view of the key-value pairs in the native object. This is the environment implementation of :meth:`BaseDict.items`. @@ -687,7 +686,7 @@ def _items(self) -> ItemsView[KeyType, ValueType]: """ self.raiseNotImplementedError() - def values(self) -> BaseValues[ValueType]: + def values(self) -> BaseValues: """Return a view of the values in the object. :return: A :class:`BaseValues` object instance. @@ -695,7 +694,7 @@ def values(self) -> BaseValues[ValueType]: """ return self._values() - def _values(self) -> BaseValues[ValueType]: + def _values(self) -> BaseValues: """Return a view of the values in the native object. This is the environment implementation of :meth:`BaseDict.values`. @@ -711,7 +710,7 @@ def _values(self) -> BaseValues[ValueType]: """ return BaseValues(self) - def __contains__(self, key: KeyType) -> bool: + def __contains__(self, key: Any) -> bool: """Check if a key is in the object. :param key: The key to check for. @@ -721,7 +720,7 @@ def __contains__(self, key: KeyType) -> bool: key = self._normalizeKey(key) return self._contains(key) - def _contains(self, key: KeyType) -> bool: + def _contains(self, key: Any) -> bool: """Check if a key is in the native object. This is the environment implementation of :meth:`BaseDict.__contains__`. @@ -740,7 +739,7 @@ def _contains(self, key: KeyType) -> bool: has_key = __contains__ - def __setitem__(self, key: KeyType, value: ValueType) -> None: + def __setitem__(self, key: Any, value: Any) -> None: """Set the value for a given key in the object. :param key: The key to set. @@ -751,7 +750,7 @@ def __setitem__(self, key: KeyType, value: ValueType) -> None: value = self._normalizeValue(value) self._setItem(key, value) - def _setItem(self, key: KeyType, value: ValueType) -> None: + def _setItem(self, key: Any, value: Any) -> None: """Set the value for a given key in the native object. This is the environment implementation of :meth:`BaseDict.__setitem__`. @@ -771,7 +770,7 @@ def _setItem(self, key: KeyType, value: ValueType) -> None: """ self.raiseNotImplementedError() - def __getitem__(self, key: KeyType) -> ValueType: + def __getitem__(self, key: Any) -> Any: """Get the value for a given key from the object. :param key: The key to retrieve the value for. @@ -782,7 +781,7 @@ def __getitem__(self, key: KeyType) -> ValueType: value = self._getItem(key) return self._normalizeValue(value) - def _getItem(self, key: KeyType) -> ValueType: + def _getItem(self, key: Any) -> Any: """Get the value for a given key from the native object. This is the environment implementation of :meth:`BaseDict.__getitem__`. @@ -803,7 +802,7 @@ def _getItem(self, key: KeyType) -> ValueType: """ self.raiseNotImplementedError() - def get(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[ValueType]: + def get(self, key: Any, default: Optional[Any] = None) -> Any: """Get the value for a given key in the object. If the given key is not found, The specified `default` will be returned. @@ -818,11 +817,11 @@ def get(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[Val key = self._normalizeKey(key) default = self._normalizeValue(default) if default is not None else default value = self._get(key, default=default) - if value is not None and value is not default: + if value is not default: value = self._normalizeValue(value) return value - def _get(self, key: KeyType, default: Optional[ValueType]) -> Optional[ValueType]: + def _get(self, key: Any, default: Optional[Any]) -> Any: """Get the value for a given key in the native object. This is the environment implementation of :meth:`BaseDict.get`. @@ -843,7 +842,7 @@ def _get(self, key: KeyType, default: Optional[ValueType]) -> Optional[ValueType return self[key] return default - def __delitem__(self, key: KeyType) -> None: + def __delitem__(self, key: Any) -> None: """Delete a key-value pair from the object. :param key: The key to delete. @@ -852,7 +851,7 @@ def __delitem__(self, key: KeyType) -> None: key = self._normalizeKey(key) self._delItem(key) - def _delItem(self, key: KeyType) -> None: + def _delItem(self, key: Any) -> None: """Delete a key-value pair from the native object. This is the environment implementation of :meth:`BaseDict.__delitem__`. @@ -869,7 +868,7 @@ def _delItem(self, key: KeyType) -> None: """ self.raiseNotImplementedError() - def pop(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[ValueType]: + def pop(self, key: Any, default: Optional[Any] = None) -> Any: """Remove a key from the object and return it's value. If the given key is not found, The specified `default` will be returned. @@ -885,11 +884,9 @@ def pop(self, key: KeyType, default: Optional[ValueType] = None) -> Optional[Val if default is not None: default = self._normalizeValue(default) value = self._pop(key, default=default) - if value is not None: - value = self._normalizeValue(value) - return value + return self._normalizeValue(value) - def _pop(self, key: KeyType, default: Optional[ValueType]) -> Optional[ValueType]: + def _pop(self, key: Any, default: Optional[Any]) -> Any: """Remove a key from the native object and return it's value. This is the environment implementation of :meth:`BaseDict.pop`. @@ -912,7 +909,7 @@ def _pop(self, key: KeyType, default: Optional[ValueType]) -> Optional[ValueType del self[key] return value - def __iter__(self) -> Iterator[KeyType]: + def __iter__(self) -> Iterator[Any]: """Return an iterator over the keys of the object. This method yields each key one by one, removing it from the list of @@ -923,7 +920,7 @@ def __iter__(self) -> Iterator[KeyType]: """ return self._iter() - def _iter(self) -> Iterator[KeyType]: + def _iter(self) -> Iterator[Any]: """Return an iterator over the keys of the native object. This is the environment implementation of :meth:`BaseDict.__iter__`. @@ -938,7 +935,7 @@ def _iter(self) -> Iterator[KeyType]: for key in self.keys(): yield key - def update(self, other: MutableMapping[KeyType, ValueType]) -> None: + def update(self, other: MutableMapping) -> None: """Update the current object instance with key-value pairs from another. :param other: A :class:`MutableMapping` of key-value pairs to update @@ -954,7 +951,7 @@ def update(self, other: MutableMapping[KeyType, ValueType]) -> None: otherCopy = d self._update(otherCopy) - def _update(self, other: MutableMapping[KeyType, ValueType]) -> None: + def _update(self, other: MutableMapping) -> None: """Update the current native object instance with key-value pairs from another. This is the environment implementation of :meth:`BaseDict.update`. @@ -990,7 +987,7 @@ def _clear(self) -> None: del self[key] -class TransformationMixin: +class TransformationMixin(ABC): """Provide objects transformation-related functionality.""" # --------------- @@ -1248,8 +1245,16 @@ def _skewBy( t = transform.Identity.skew(x=x, y=y) self.transformBy(tuple(t), origin=origin, **kwargs) + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + -class InterpolationMixin: +class InterpolationMixin(ABC): """Provide objects with interpolation-related functionality. :cvar compatibilityReporterClass: A class used for reporting interpolation @@ -1303,8 +1308,16 @@ def _isCompatible(self, other: Any, reporter: Any) -> None: """ self.raiseNotImplementedError() + # ---------------- + # Abstract members + # ---------------- -class SelectionMixin: + @abstractmethod + def raiseNotImplementedError(self): + pass + + +class SelectionMixin(ABC): """Provide objects with selection-related functionality.""" # ------------- @@ -1389,8 +1402,16 @@ def _setSelectedSubObjects( for obj in subObjects: obj.selected = obj in selected + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + -class PointPositionMixin: +class PointPositionMixin(ABC): """Provide objects with the ability to determine point position. This class adds a `position` attribute as a :class:`dyanmicProperty`, for @@ -1456,8 +1477,30 @@ def _set_position(self, value: PairCollectionType[IntFloatType]) -> None: dY = y - pY self.moveBy((dX, dY)) + # ---------------- + # Abstract members + # ---------------- -class IdentifierMixin: + @abstractmethod + @property + def x(self): + pass + + @abstractmethod + @property + def y(self): + pass + + @abstractmethod + def moveBy(self, value): + pass + + @abstractmethod + def raiseNotImplementedError(self): + pass + + +class IdentifierMixin(ABC): """Provide objects with a unique identifier.""" # identifier @@ -1548,6 +1591,14 @@ def _setIdentifier(self, value: str) -> None: """ pass + # ---------------- + # Abstract members + # ---------------- + + @abstractmethod + def raiseNotImplementedError(self): + pass + def reference(obj: Callable[[], Any]) -> Callable[[], Any]: """ From ac33aed748345a5005c223476e81c92ac4ace237 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Wed, 4 Dec 2024 16:42:15 +0100 Subject: [PATCH 08/14] Revert to v1. --- Lib/fontParts/base/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/fontParts/base/lib.py b/Lib/fontParts/base/lib.py index 544b3299..6329bfb4 100644 --- a/Lib/fontParts/base/lib.py +++ b/Lib/fontParts/base/lib.py @@ -310,7 +310,7 @@ def clear(self) -> None: """ super(BaseLib, self).clear() - def get(self, key: str, default: Optional[LibValueType] = None) -> Optional[LibValueType]: + def get(self, key: str, default: Optional[LibValueType] = None) -> LibValueType: """Get the value for the given key in the lib. If the given `key` is not found, The specified `default` will be returned. @@ -386,7 +386,7 @@ def values(self) -> BaseValues[LibValueType]: """ return super(BaseLib, self).values() - def pop(self, key: str, default: Optional[LibValueType] = None) -> Optional[LibValueType]: + def pop(self, key: str, default: Optional[LibValueType] = None) -> LibValueType: """Remove the specified key and return its associated value. If the `key` does not exist, the `default` value is returned. From c152b9f99ed7b2906818e63174efff7c4128a254 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Wed, 4 Dec 2024 19:14:03 +0100 Subject: [PATCH 09/14] Correct implementation errors. - Add dynamic abstract dynamic properties to `PointPositionMixin`. - Remove unused `PointPositionMixin` from `BaseImage`. --- Lib/fontParts/base/base.py | 34 ++++++++++++++++++++++++++++++---- Lib/fontParts/base/image.py | 2 -- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 6ee5b294..81957c52 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1481,14 +1481,40 @@ def _set_position(self, value: PairCollectionType[IntFloatType]) -> None: # Abstract members # ---------------- + x: dynamicProperty = dynamicProperty("base_x") + + @abstractmethod + def _get_base_x(self) -> IntFloatType: + pass + + @abstractmethod + def _set_base_x(self, value: IntFloatType) -> None: + pass + + @abstractmethod + def _get_x(self) -> IntFloatType: + pass + + @abstractmethod + def _set_x(self, value: IntFloatType) -> None: + pass + + y: dynamicProperty = dynamicProperty("base_y") + + @abstractmethod + def _get_base_y(self) -> IntFloatType: + pass + + @abstractmethod + def _set_base_y(self, value: IntFloatType) -> None: + pass + @abstractmethod - @property - def x(self): + def _get_y(self) -> IntFloatType: pass @abstractmethod - @property - def y(self): + def _set_y(self, value: IntFloatType) -> None: pass @abstractmethod diff --git a/Lib/fontParts/base/image.py b/Lib/fontParts/base/image.py index 21afa275..5509b826 100644 --- a/Lib/fontParts/base/image.py +++ b/Lib/fontParts/base/image.py @@ -2,7 +2,6 @@ from fontParts.base.base import ( BaseObject, TransformationMixin, - PointPositionMixin, SelectionMixin, dynamicProperty, reference, @@ -15,7 +14,6 @@ class BaseImage( BaseObject, TransformationMixin, - PointPositionMixin, SelectionMixin, DeprecatedImage, RemovedImage, From 0837023f0b659ba19b677138e074a14bd2a05a4e Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 09:28:27 +0100 Subject: [PATCH 10/14] Implement abstract members. --- Lib/fontParts/base/base.py | 6 ++--- Lib/fontParts/base/contour.py | 5 ++-- Lib/fontParts/base/font.py | 30 +++++++++++------------ Lib/fontParts/base/layer.py | 45 +++++++++++++++++++++++++++++++++-- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 97441cdd..6b5ea03d 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -189,7 +189,7 @@ def interpolate( # ------------ -class BaseObject: +class BaseObject(Generic[BaseObjectType]): r"""Provide common base functionality to objects. This class is intended to serve as a foundation for other classes, supplying @@ -1399,13 +1399,13 @@ def _set_selected(self, value: bool) -> None: # Sub-Objects # ----------- @classmethod - def _getSelectedSubObjects(cls, subObjects: CollectionType[Any]) -> Tuple[Any]: + def _getSelectedSubObjects(cls, subObjects: Any) -> Tuple[Any]: selected = tuple(obj for obj in subObjects if obj.selected) return selected @classmethod def _setSelectedSubObjects( - cls, subObjects: CollectionType[Any], selected: CollectionType[Any] + cls, subObjects: Any, selected: Any ) -> None: for obj in subObjects: obj.selected = obj in selected diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index d55ab19d..db8c66b7 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, cast, Any, Iterator, List, Optional, Tuple, Union +from typing import (TYPE_CHECKING, cast, Any, Iterator, List, Optional, Tuple, TypeVar, Union) from fontParts.base.errors import FontPartsError from fontParts.base.base import ( @@ -32,6 +32,7 @@ from fontParts.base.layer import BaseLayer from fontParts.base.font import BaseFont +BaseContourType = TypeVar("BaseContourType", bound="BaseContour") PointCollectionType = CollectionType[PairCollectionType[IntFloatType]] @@ -65,7 +66,7 @@ def _reprContents(self) -> List[str]: contents += self.glyph._reprContents() return contents - def copyData(self, source: BaseContour) -> None: + def copyData(self, source: BaseContourType) -> None: """Copy data from another contour instance. This will copy the contents of the following attributes from `source` diff --git a/Lib/fontParts/base/font.py b/Lib/fontParts/base/font.py index 620d1513..37f67ca8 100644 --- a/Lib/fontParts/base/font.py +++ b/Lib/fontParts/base/font.py @@ -204,7 +204,7 @@ def _get_base_path(self) -> Optional[str]: path = normalizers.normalizeFilePath(path) return path - def _get_path(self, **kwargs: Any) -> Optional[str]: + def _get_path(self, **kwargs: Any) -> Optional[str]: # type: ignore[return] r"""Get the path to the native font file. This method is the environment implementation @@ -574,7 +574,7 @@ def _get_base_info(self) -> BaseInfo: info.font = self return info - def _get_info(self) -> BaseInfo: + def _get_info(self) -> BaseInfo: # type: ignore[return] """Get the native font's info object. This is the environment implementation of :attr:`BaseFont.info`. @@ -613,7 +613,7 @@ def _get_base_groups(self) -> BaseGroups: groups.font = self return groups - def _get_groups(self) -> BaseGroups: + def _get_groups(self) -> BaseGroups: # type: ignore[return] """Get the native font's groups object. This is the environment implementation @@ -653,7 +653,7 @@ def _get_base_kerning(self) -> BaseKerning: kerning.font = self return kerning - def _get_kerning(self) -> BaseKerning: + def _get_kerning(self) -> BaseKerning: # type: ignore[return] """Get the native font's kerning object. This is the environment implementation @@ -752,7 +752,7 @@ def _get_base_features(self) -> BaseFeatures: features.font = self return features - def _get_features(self) -> BaseFeatures: + def _get_features(self) -> BaseFeatures: # type: ignore[return] """Get the native font's features object. This is the environment implementation of @@ -792,7 +792,7 @@ def _get_base_lib(self) -> BaseLib: lib.font = self return lib - def _get_lib(self) -> BaseLib: + def _get_lib(self) -> BaseLib: # type: ignore[return] """Get the native font's lib object. This is the environment implementation of :attr:`BaseFont.lib`. @@ -837,7 +837,7 @@ def _get_base_tempLib(self) -> BaseLib: lib.font = self return lib - def _get_tempLib(self) -> BaseLib: + def _get_tempLib(self) -> BaseLib: # type: ignore[return] """Get the native font's temporary lib object. This is the environment implementation @@ -883,7 +883,7 @@ def _get_base_layers(self) -> Tuple[BaseLayer, ...]: self._setFontInLayer(layer) return tuple(layers) - def _get_layers(self, **kwargs: Any) -> Tuple[BaseLayer, ...]: + def _get_layers(self, **kwargs: Any) -> Tuple[BaseLayer, ...]: # type: ignore[return] r"""Get the native font's layer objects. This is the environment implementation of @@ -932,7 +932,7 @@ def _set_base_layerOrder(self, value: CollectionType[str]) -> None: value = normalizers.normalizeLayerOrder(value, self) self._set_layerOrder(value) - def _get_layerOrder(self, **kwargs: Any) -> Tuple[str, ...]: + def _get_layerOrder(self, **kwargs: Any) -> Tuple[str, ...]: # type: ignore[return] r"""Get the order of the layers in the native font. This is the environment implementation of the @@ -1006,7 +1006,7 @@ def _set_base_defaultLayerName(self, value: str) -> None: value = normalizers.normalizeDefaultLayerName(value, self) self._set_defaultLayerName(value) - def _get_defaultLayerName(self) -> str: + def _get_defaultLayerName(self) -> str: # type: ignore[return] """Get the name of the native font's default layer. This is the environment implementation of @@ -1181,7 +1181,7 @@ def newLayer( self._setFontInLayer(layer) return layer - def _newLayer( + def _newLayer( # type: ignore[return] self, name: str, color: Optional[QuadrupleCollectionType[IntFloatType]], @@ -1577,7 +1577,7 @@ def _set_base_glyphOrder(self, value: CollectionType[str]) -> None: value = normalizers.normalizeGlyphOrder(value) self._set_glyphOrder(value) - def _get_glyphOrder(self) -> Tuple[str, ...]: + def _get_glyphOrder(self) -> Tuple[str, ...]: # type: ignore[return] r"""Get the order of the glyphs in the native font. This is the environment implementation of the @@ -1732,7 +1732,7 @@ def _get_guidelines(self) -> Tuple[BaseGuideline, ...]: def _len__guidelines(self) -> int: return self._lenGuidelines() - def _lenGuidelines(self, **kwargs: Any) -> int: + def _lenGuidelines(self, **kwargs: Any) -> int: # type: ignore[return] r"""Return the number of font-level guidelines in the native font. :param \**kwargs: Additional keyword arguments. @@ -1754,7 +1754,7 @@ def _getitem__guidelines(self, index: int) -> BaseGuideline: self._setFontInGuideline(guideline) return guideline - def _getGuideline(self, index: int, **kwargs: Any) -> BaseGuideline: + def _getGuideline(self, index: int, **kwargs: Any) -> BaseGuideline: # type: ignore[return] r"""Return the guideline at the given index. :param index: The index of the guideline. @@ -1846,7 +1846,7 @@ def appendGuideline( newGuideline.font = self return newGuideline - def _appendGuideline( + def _appendGuideline( # type: ignore[return] self, position: Optional[PairCollectionType[IntFloatType]], angle: Optional[float], diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index 93703ef5..d696f8ac 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1,6 +1,7 @@ # pylint: disable=C0103, C0302, C0114, W0613 from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Optional, Tuple +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Callable, Iterator, List, NoReturn, Optional, Tuple, TypeVar import collections from fontParts.base.base import ( @@ -29,7 +30,7 @@ from fontParts.base.lib import BaseLib -class _BaseGlyphVendor(BaseObject, SelectionMixin): +class _BaseGlyphVendor(BaseObject, SelectionMixin, ABC): """Provide common glyph interaction. This class provides common glyph interaction code to the @@ -573,6 +574,28 @@ def _set_selectedGlyphNames(self, value: CollectionType[str]) -> None: has_key: Callable[[_BaseGlyphVendor, str], bool] = __contains__ + # ---------------- + # Abstract Members + # ---------------- + + defaultLayer: dynamicProperty = dynamicProperty("base_defaultLayer") + + @abstractmethod + def _get_base_defaultLayer(self) -> BaseLayer: + pass + + @abstractmethod + def _set_base_defaultLayer(self, layer: BaseLayer) -> None: + pass + + @abstractmethod + def _get_defaultLayer(self) -> BaseLayer: + pass + + @abstractmethod + def _set_defaultLayer(self, value: BaseLayer) -> None: + pass + class BaseLayer(_BaseGlyphVendor, InterpolationMixin, DeprecatedLayer, RemovedLayer): """Represent the basis for a layer object. @@ -1224,3 +1247,21 @@ def _getCharacterMapping(self) -> CharacterMappingType: for code in glyph.unicodes: mapping[code].append(glyph.name) return {k: tuple(v) for k, v in mapping.items()} + + # -------------------------- + # Abstract Members Overrides + # -------------------------- + + defaultLayer: dynamicProperty = dynamicProperty("base_defaultLayer") + + def _get_base_defaultLayer(self) -> BaseLayer: + raise NotImplementedError("BaseLayer does not implement this method.") + + def _set_base_defaultLayer(self, layer: BaseLayer) -> None: + raise NotImplementedError("BaseLayer does not implement this method.") + + def _get_defaultLayer(self) -> BaseLayer: + raise NotImplementedError("BaseLayer does not implement this method.") + + def _set_defaultLayer(self, value: BaseLayer) -> None: + raise NotImplementedError("BaseLayer does not implement this method.") From 9089845809bca8a9aadd9ed2432f4be1df489d26 Mon Sep 17 00:00:00 2001 From: knutnergaard Date: Thu, 5 Dec 2024 08:29:15 +0000 Subject: [PATCH 11/14] Format fixes by ruff --- Lib/fontParts/base/base.py | 4 +--- Lib/fontParts/base/contour.py | 12 +++++++++++- Lib/fontParts/base/layer.py | 12 +++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Lib/fontParts/base/base.py b/Lib/fontParts/base/base.py index 6b5ea03d..6172c128 100644 --- a/Lib/fontParts/base/base.py +++ b/Lib/fontParts/base/base.py @@ -1404,9 +1404,7 @@ def _getSelectedSubObjects(cls, subObjects: Any) -> Tuple[Any]: return selected @classmethod - def _setSelectedSubObjects( - cls, subObjects: Any, selected: Any - ) -> None: + def _setSelectedSubObjects(cls, subObjects: Any, selected: Any) -> None: for obj in subObjects: obj.selected = obj in selected diff --git a/Lib/fontParts/base/contour.py b/Lib/fontParts/base/contour.py index db8c66b7..8d513f5f 100644 --- a/Lib/fontParts/base/contour.py +++ b/Lib/fontParts/base/contour.py @@ -1,5 +1,15 @@ from __future__ import annotations -from typing import (TYPE_CHECKING, cast, Any, Iterator, List, Optional, Tuple, TypeVar, Union) +from typing import ( + TYPE_CHECKING, + cast, + Any, + Iterator, + List, + Optional, + Tuple, + TypeVar, + Union, +) from fontParts.base.errors import FontPartsError from fontParts.base.base import ( diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index d696f8ac..acbd4957 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1,7 +1,17 @@ # pylint: disable=C0103, C0302, C0114, W0613 from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Callable, Iterator, List, NoReturn, Optional, Tuple, TypeVar +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + NoReturn, + Optional, + Tuple, + TypeVar, +) import collections from fontParts.base.base import ( From 746f6f146c03930d17e08258b4638ed9111483c3 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 09:31:14 +0100 Subject: [PATCH 12/14] Correct typo. --- Lib/fontParts/base/layer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index d696f8ac..c94e4a6a 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1249,7 +1249,7 @@ def _getCharacterMapping(self) -> CharacterMappingType: return {k: tuple(v) for k, v in mapping.items()} # -------------------------- - # Abstract Members Overrides + # Abstract Member Overrides # -------------------------- defaultLayer: dynamicProperty = dynamicProperty("base_defaultLayer") From 65f78dedb17166d4b3b5b4a538fc7214bd4da782 Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 09:37:49 +0100 Subject: [PATCH 13/14] Delete superflous dashes. --- Lib/fontParts/base/layer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index 4b9cf7f0..beaeb41f 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -1258,9 +1258,9 @@ def _getCharacterMapping(self) -> CharacterMappingType: mapping[code].append(glyph.name) return {k: tuple(v) for k, v in mapping.items()} - # -------------------------- + # ------------------------- # Abstract Member Overrides - # -------------------------- + # ------------------------- defaultLayer: dynamicProperty = dynamicProperty("base_defaultLayer") From 604c69ed1e46176a4fa44b0489d6fde18a914adb Mon Sep 17 00:00:00 2001 From: Knut Nergaard Date: Thu, 5 Dec 2024 09:47:55 +0100 Subject: [PATCH 14/14] Delete unused imports. --- Lib/fontParts/base/layer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/fontParts/base/layer.py b/Lib/fontParts/base/layer.py index beaeb41f..6b008105 100644 --- a/Lib/fontParts/base/layer.py +++ b/Lib/fontParts/base/layer.py @@ -7,10 +7,8 @@ Callable, Iterator, List, - NoReturn, Optional, Tuple, - TypeVar, ) import collections